Micronaut JWT Authentication

Learn how to secure a Micronaut app using JWT (JSON Web Token) Authentication.

Authors: Sergio del Amo

Micronaut Version: 1.0.0.M4

1 Getting Started

Micronaut ships with security capabilities based on Json Web Token (JWT). JWT is an IETF standard which defines a secure way to encapsulate arbitrary data that can be sent over unsecure URL’s.

In this guide you are going to create a Micronaut app and secure it with JWT.

The following sequence illustrates the authentication flow:

jwt bearer token

1.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

1.2 Solution

We recommend you to follow the instructions in the next sections and create the app step by step. However, you can go right to the completed example.

or

Then, cd into the complete folder which you will find in the root project of the downloaded/cloned project.

2 Writing the Application

Create an app using the Micronaut Command Line Interface.

mn create-app example.micronaut.complete

The previous command createas a micronaut app with the default package example.micronaut in a folder named complete.

By default, create-app generates a Java Micronaut app and it uses Gradle build system. However, you could use other build tool such as Maven or other programming languages such as Groovy or Kotlin.

If you are using Java or Kotlin and IntelliJ IDEA make sure you have enabled annotation processing.

annotationprocessorsintellij

2.1 Security Dependency

Add Micronaut’s security-jwt dependency to your build file.

build.gradle
dependencies {
...
..
.
    compile "io.micronaut:security-jwt"
}

2.2 Configuration

Create the a new application.yml configuration file:

src/main/resources/application.yml
micronaut:
  security:
    enabled: true (1)
    endpoints:
      login:
        enabled: true (2)
      oauth:
        enabled: true (3)
    token:
      jwt:
        enabled: true (4)
        signatures:
          secret:
            generator: (5)
              secret: pleaseChangeThisSecretForANewOne (6)
1 Enable Micronaut’s security capabilities
2 Expose /login endpoint
3 Expose /oauth/access_token endpoint as defined by section 6 of the OAuth 2.0 spec - Refreshing an Access Token.
4 Enable JWT based authentication
5 You can create a SecretSignatureConfiguration named generator via configuration as illustrated above. The generator signature is used to sign the issued JWT claims.
6 Change this by your own secret and keep it safe (do not store this in your VCS)

2.3 Authentication Provider

To keep this guide simple, create a naive AuthenticationProvider to simulate user’s authentication.

src/main/java/example/micronaut/services/AuthenticationProviderUserPassword.java
package example.micronaut.services;

import io.micronaut.security.authentication.AuthenticationFailed;
import io.micronaut.security.authentication.AuthenticationProvider;
import io.micronaut.security.authentication.AuthenticationRequest;
import io.micronaut.security.authentication.AuthenticationResponse;
import io.micronaut.security.authentication.UserDetails;
import io.reactivex.Flowable;
import org.reactivestreams.Publisher;

import javax.inject.Singleton;
import java.util.ArrayList;

@Singleton (1)
public class AuthenticationProviderUserPassword implements AuthenticationProvider  { (2)

    @Override
    public Publisher<AuthenticationResponse> authenticate(AuthenticationRequest authenticationRequest) {
        if ( authenticationRequest.getIdentity().equals("sherlock") &&
                authenticationRequest.getSecret().equals("password") ) {
            return Flowable.just(new UserDetails((String) authenticationRequest.getIdentity(), new ArrayList<>()));
        }
        return Flowable.just(new AuthenticationFailed());
    }
}
1 To register a Singleton in Micronaut’s application context, annotate your class with javax.inject.Singleton
2 A Micronaut’s Authentication Provider implements the interface io.micronaut.security.authentication.AuthenticationProvider

2.4 Controllers

Create a file named HomeController which resolves the base URL /:

src/main/java/example/micronaut/controllers/HomeController.java
package example.micronaut.controllers;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.Secured;
import java.security.Principal;

@Secured("isAuthenticated()") (1)
@Controller("/")  (2)
public class HomeController {

    @Get("/")  (3)
    String index(Principal principal) {  (4)
        return principal.getName();
    }
}
1 Annotate with io.micronaut.security.Secured to configure secured access. The isAuthenticated() expression will allow access only to authenticated users.
2 Annotate with io.micronaut.http.annotation.Controller to designate a class as a Micronaut’s controller.
3 You can specify the HTTP verb that a controller’s action responds to. To respond to a GET request, use the io.micronaut.http.annotation.Get annotation.
4 If a user is authenticated, Micronaut will bind the user object to an argument of type java.security.Principal (if present).

3 Tests

Micronaut is test framework agnostic. You can use JUnit, Spock Framework or Spek.

In this Guide, we test the app with Spock Framework.

We need to modify build.gradle.

Replace apply plugin: 'java' with apply plugin: 'groovy' and add the necessary dependencies:

build.gradle
dependencies {
...
..
    testCompile "org.codehaus.groovy:groovy-all:2.4.15"
    testCompile "org.spockframework:spock-core:1.1-groovy-2.4"
}

Edit micronaut-cli.yml to set Spock as the test framework:

micronaut-cli.yml
profile: service
defaultPackage: example
---
testFramework: spock
sourceLanguage: java

Create a test to verify a user is able to login and access a secured endpoint.

src/test/groovy/example/micronaut/JwtAuthenticationSpec.groovy
package example.micronaut

import com.nimbusds.jwt.JWTParser
import com.nimbusds.jwt.SignedJWT
import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpHeaders
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.security.authentication.UsernamePasswordCredentials
import io.micronaut.security.token.jwt.render.BearerAccessRefreshToken
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.HttpRequest

class JwtAuthenticationSpec extends Specification {

    @Shared
    @AutoCleanup (1)
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) (2)

    @Shared
    @AutoCleanup
    RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) (3)

    def "Verify JWT authentication works"() {
        when: 'Accessing a secured URL without authenticating'
        client.toBlocking().exchange(HttpRequest.GET('/', )) (4)

        then: 'returns unauthorized'
        HttpClientResponseException e = thrown(HttpClientResponseException)
        e.status == HttpStatus.UNAUTHORIZED

        when: 'Login endpoint is called with valid credentials'
        UsernamePasswordCredentials creds = new UsernamePasswordCredentials("sherlock", "password")
        HttpRequest request = HttpRequest.POST('/login', creds) (5)
        HttpResponse<BearerAccessRefreshToken> rsp = client.toBlocking().exchange(request, BearerAccessRefreshToken) (6)

        then: 'the endpoint can be accessed'
        rsp.status == HttpStatus.OK
        rsp.body().username == 'sherlock'
        rsp.body().accessToken
        JWTParser.parse(rsp.body().accessToken) instanceof SignedJWT
        rsp.body().refreshToken
        JWTParser.parse(rsp.body().refreshToken) instanceof SignedJWT

        when:
        String accessToken = rsp.body().accessToken
        HttpRequest requestWithAuthorization = HttpRequest.GET('/' ).header(HttpHeaders.AUTHORIZATION, "Bearer $accessToken") (7)
        HttpResponse<String> response = client.toBlocking().exchange(requestWithAuthorization, String)

        then:
        response.status == HttpStatus.OK
        response.body() == 'sherlock' (8)
    }
}
1 The AutoCleanup extension makes sure the close() method of an object (e.g. EmbeddedServer) is called each time a feature method is finished
2 To run the application from a unit test you can use the EmbeddedServer interface
3 Register a RxClient bean in the application context and point it to the embedded server URL. The EmbeddedServer interface provides the URL of the server under test which runs on a random port.
4 Once you turn on security, every endpoint is secured by default.
5 To login, do a POST request to /login with your credentials as a JSON payload in the body of the request.
6 Micronaut makes it easy to parse JSON into Java objects.
7 Micronaut supports RFC 6750 Bearer Token specification out-of-the-box. We supply the JWT token in the Authorization HTTP Header.
8 Use .body() to retrieve the parsed payload.

3.1 Token Refresh

Create a new test to verify that token refreshing works as expected.

src/test/groovy/example/micronaut/OauthAccessTokenSpec.groovy
package example.micronaut

import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.RxHttpClient
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.security.authentication.UsernamePasswordCredentials
import io.micronaut.security.token.jwt.endpoints.TokenRefreshRequest
import io.micronaut.security.token.jwt.render.AccessRefreshToken
import io.micronaut.security.token.jwt.render.BearerAccessRefreshToken
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class OauthAccessTokenSpec extends Specification {

    @Shared
    @AutoCleanup
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    @Shared
    @AutoCleanup
    RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())

    def "Verify JWT access token refresh works"() {
        when: 'login endpoint is called with valid credentials'
        UsernamePasswordCredentials creds = new UsernamePasswordCredentials("sherlock", "password")
        HttpRequest request = HttpRequest.POST('/login', creds)
        HttpResponse<BearerAccessRefreshToken> rsp = client.toBlocking().exchange(request, BearerAccessRefreshToken)

        then: 'the endpoint can be accessed'
        rsp.status == HttpStatus.OK

        when:
        sleep(1_000) // sleep for one second to give time for the issued at `iat` Claim to change
        String refreshToken = rsp.body().refreshToken
        String accessToken = rsp.body().accessToken

        HttpResponse<AccessRefreshToken> response = client.toBlocking().exchange(HttpRequest.POST('/oauth/access_token',
                new TokenRefreshRequest("refresh_token", refreshToken)), AccessRefreshToken) (1)

        then:
        response.status == HttpStatus.OK
        response.body().accessToken
        response.body().accessToken != accessToken (2)
    }
}
}
1 Make a POST request to /oauth/access_token with the refresh token in the JSON payload to get a new access token
2 A different access token is retrieved.

3.2 Use Micronaut's HTTP Client and JWT

If you want to access a secured endpoint, you can also use Micronaut’s HTTP Client and supply the JWT token in the Authorization header.

First create a @Client with a method home which accepts an Authorization HTTP Header.

src/test/java/example/micronaut/AppClient.java
package example.micronaut;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Header;
import io.micronaut.http.client.Client;

@Client("/")
public interface AppClient {

    @Get("/")
    String home(@Header String authorization);
}

Create a test which uses the previous @Client

src/test/groovy/example/micronaut/DeclarativeHttpClientWithJwtSpec.groovy
package example.micronaut

import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.security.authentication.UsernamePasswordCredentials
import io.micronaut.security.token.jwt.render.BearerAccessRefreshToken
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class DeclarativeHttpClientWithJwtSpec extends Specification {

    @Shared
    @AutoCleanup (1)
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) (2)

    @Shared
    @AutoCleanup
    RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) (3)

    def "Verify JWT authentication works with declarative @Client"() {
        when:
        AppClient appClient = embeddedServer.applicationContext.getBean(AppClient) (4)

        then:
        noExceptionThrown()

        when: 'Accessing a secured URL without authenticating'
        client.toBlocking().exchange(HttpRequest.GET('/', ))

        then: 'returns unauthorized'
        HttpClientResponseException e = thrown(HttpClientResponseException)
        e.status == HttpStatus.UNAUTHORIZED

        when: 'Login endpoint is called with valid credentials'
        UsernamePasswordCredentials creds = new UsernamePasswordCredentials("sherlock", "password")
        HttpRequest request = HttpRequest.POST('/login', creds) (5)
        HttpResponse<BearerAccessRefreshToken> rsp = client.toBlocking().exchange(request, BearerAccessRefreshToken) (6)

        then: 'the endpoint can be accessed'
        rsp.status == HttpStatus.OK
        rsp.body().accessToken

        when:
        String accessToken = rsp.body().accessToken
        String authorizationValue = "Bearer $accessToken"
        String msg = appClient.home(authorizationValue) (7)

        then:
        msg == 'sherlock' (8)
    }
}
1 The AutoCleanup extension makes sure the close() method of an object (e.g. EmbeddedServer) is called each time a feature method is finished
2 To run the application from a unit test you can use the EmbeddedServer interface
3 Register a RxClient bean in the application context and point it to the embedded server URL. The EmbeddedServer interface provides the URL of the server under test which runs on a random port.
4 Retrieve AppClient bean from application context.
5 To login, do a POST request to /login with your credentials as a JSON payload in the body of the request.
6 Micronaut makes it easy to parse JSON into Java objects.
7 Supply the JWT to the HTTP Authorization header value to the @Client method.
8 Use .body() to retrieve the parsed payload.

4 Testing the Application

To run the tests:

$ ./gradlew test
$ open build/reports/tests/test/index.html

5 Running the Application

To run the application use the ./gradlew run command which will start the application on a random port.