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 a Kotlin Micronaut app using the Micronaut Command Line Interface.

mn create-app example.micronaut.complete --features=kotlin

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

Due to the --features kotlin flag, it generates a Kotlin Micronaut app and it uses Gradle build system. However, you could use other build tool such as Maven or other programming languages such as Java or Groovy.

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

annotationprocessorsintellij

Kotlin, Kapt and IntelliJ

As of this writing IntelliJ’s built-in compiler does not directly support Kapt and annotation processing. You must instead configure Intellij to run Gradle (or Maven) compilation as a build step before running your tests or application class.

First edit the run configuration for tests or for the application and select "Run Gradle task" as a build step:

Intellij Settings

Then add the classes task as task to execute for the application or for tests the testClasses task:

Intellij Settings

Now whenever you run tests or the application Micronaut classes will be generated at compilation time.

Read Micronaut Kotlin section to learn more.

Alternatively, you can delegate IntelliJ build/run actions to gradle completely:

delegatetogradle

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/kotlin/example/micronaut/services/AuthenticationProviderUserPassword.kt
package example.micronaut.services

import io.micronaut.security.authentication.AuthenticationProvider
import io.micronaut.security.authentication.AuthenticationRequest
import io.micronaut.security.authentication.AuthenticationResponse
import org.reactivestreams.Publisher
import io.micronaut.security.authentication.AuthenticationFailed
import io.reactivex.Flowable
import java.util.ArrayList
import io.micronaut.security.authentication.UserDetails
import javax.inject.Singleton

@Singleton (1)
class AuthenticationProviderUserPassword : AuthenticationProvider { (2)
    override fun authenticate(authenticationRequest: AuthenticationRequest<*, *>?): Publisher<AuthenticationResponse> {
        if (authenticationRequest!=null && authenticationRequest.identity != null && authenticationRequest.secret != null) {
            if ( authenticationRequest.identity == "sherlock" && authenticationRequest.secret == "password" ) {
                return Flowable.just<AuthenticationResponse>(UserDetails(authenticationRequest.identity as String, ArrayList()))
            }
        }
        return Flowable.just<AuthenticationResponse>(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/kotlin/example/micronaut/controllers/HomeController.kt
package example.micronaut.controllers

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

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

    @Produces(MediaType.TEXT_PLAIN) (3)
    @Get("/")  (4)
    fun index(principal: Principal) : String {  (5)
        return principal.name
    }
}
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 By default a Micronaut’s response uses application/json as Content-Type. We are returning a String not a JSON object. Because of that, we set it to text/plain.
4 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.
5 If a user is authenticated, Micronaut will bind the user object to an argument of type java.security.Principal (if present).

3 Tests

In this tutorial, we use Spek 2 to test the microservices:

gradle.properties
micronautVersion=1.0.0.M4
kotlinVersion=1.2.61
spekVersion=2.0.0-alpha.1
junitVersion=5.1.0
bookcatalogue/build.gradle
repositories {
...
..
    maven { url "https://dl.bintray.com/spekframework/spek-dev" }
}
dependencies {
    ...
    ..
    .
    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
    testImplementation "org.spekframework.spek2:spek-dsl-jvm:$spekVersion"
    testRuntimeOnly "org.spekframework.spek2:spek-runner-junit5:$spekVersion"
}

test {
    useJUnitPlatform {
        includeEngines 'spek2'
    }
}

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

src/test/kotlin/example/micronaut/JwtAuthenticationSpec.kt
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.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 org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class BasicAuthSpec: Spek({
    describe("Verify JWT authentication works") {

        var embeddedServer : EmbeddedServer = ApplicationContext.run(EmbeddedServer::class.java) (1)
        var client : RxHttpClient = RxHttpClient.create(embeddedServer.url) (2)
        it("Accessing a secured URL without authenticating") {
            var exceptionThrown = false
            try {
                val request = HttpRequest.GET<Any>("/")
                client.toBlocking().exchange(request, String::class.java)
            } catch(e: HttpClientResponseException) {  (3)
                exceptionThrown = true
            }
            assertTrue(exceptionThrown)
        }
        it("the endpoint can be access with JWT obtained when Login endpoint is called with valid credentials") {
            val creds = UsernamePasswordCredentials("sherlock", "password")
            val request = HttpRequest.POST("/login", creds) (4)

            val rsp : HttpResponse<BearerAccessRefreshToken> = client.toBlocking().exchange(request,
                    BearerAccessRefreshToken::class.java) (5)

            assertEquals(rsp.status()!!, HttpStatus.OK)
            assertNotNull(rsp.body()!!.accessToken)
            assertTrue(JWTParser.parse(rsp.body()!!.accessToken) is SignedJWT)
            assertNotNull(rsp.body()!!.refreshToken)
            assertNotNull(JWTParser.parse(rsp.body()!!.refreshToken) is SignedJWT)

            val accessToken : String = rsp.body()!!.accessToken
            val requestWithAuthorization = HttpRequest.GET<Any>("/" ).header(HttpHeaders.AUTHORIZATION, "Bearer $accessToken") (6)
            val response : HttpResponse<String>  = client.toBlocking().exchange(requestWithAuthorization, String::class.java)

            assertEquals(response.status()!!, HttpStatus.OK)
            assertTrue(response.body()!! == "sherlock") (7)
        }
        afterGroup {
            client.close()
            embeddedServer.close()
        }

    }
})
1 To run the application from a unit test you can use the EmbeddedServer interface
2 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.
3 Once you turn on security, every endpoint is secured by default.
4 To login, do a POST request to /login with your credentials as a JSON payload in the body of the request.
5 Micronaut makes it easy to parse JSON into Java objects.
6 Micronaut supports RFC 6750 Bearer Token specification out-of-the-box. We supply the JWT token in the Authorization HTTP Header.
7 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/kotlin/example/micronaut/OauthAccessTokenSpec.kt
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 org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.lang.Thread.sleep
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class OauthAccessTokenSpec: Spek({
    describe("Verify JWT access token refresh works") {

        var embeddedServer: EmbeddedServer = ApplicationContext.run(EmbeddedServer::class.java)
        var client: RxHttpClient = RxHttpClient.create(embeddedServer.url)

        it("you can refresh JWT access token with /oauth/access_token endpoint") {
            val creds = UsernamePasswordCredentials("sherlock", "password")
            val request = HttpRequest.POST("/login", creds)

            val rsp: HttpResponse<BearerAccessRefreshToken> = client.toBlocking().exchange(request,
                    BearerAccessRefreshToken::class.java)

            assertEquals(rsp.status()!!, HttpStatus.OK)

            val refreshToken: String = rsp.body()!!.refreshToken
            val accessToken: String = rsp.body()!!.accessToken
             sleep(1_000) // sleep for one second to give time for the issued at `iat` Claim to change
            val refreshTokenRequest: HttpRequest<TokenRefreshRequest> = HttpRequest.POST("/oauth/access_token",
                    TokenRefreshRequest("refresh_token", refreshToken)) (1)
            val response: HttpResponse<AccessRefreshToken> = client.toBlocking().exchange(refreshTokenRequest, AccessRefreshToken::class.java)

            assertEquals(response.status()!!, HttpStatus.OK)
            assertNotNull(response.body()!!.accessToken)
            assertTrue(response.body()!!.accessToken != accessToken) (2)
        }

        afterGroup {
            client.close()
            embeddedServer.close()
        }
    }
})
}
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/kotlin/example/micronaut/AppClient.kt
package example.micronaut

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

@Client("/")
interface AppClient {

    @Get("/")
    fun home(@Header authorization: String): String
}

Create a test which uses the previous @Client

src/test/kotlin/example/micronaut/DeclarativeHttpClientWithJwtSpec.kt
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 org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe

import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class DeclarativeHttpClientWithJwtSpec: Spek({
    describe("Verify JWT authentication works with declarative @Client") {

        var embeddedServer: EmbeddedServer = ApplicationContext.run(EmbeddedServer::class.java) (1)
        var client: RxHttpClient = RxHttpClient.create(embeddedServer.url) (2)

        var appClient : AppClient? = null
        it("AppClient Bean can be retrieved from Application context") {
            var exceptionThrown = false
            try {
                appClient = embeddedServer.applicationContext.getBean(AppClient::class.java) (3)

            } catch(e: HttpClientResponseException) {
                exceptionThrown = true
            }
            assertFalse(exceptionThrown)
        }
        it("Accessing a secured URL without authenticating returns unauthorized") {
            var exceptionThrown = false
            try {
                val request = HttpRequest.GET<Any>("/")
                client.toBlocking().exchange(request, String::class.java)
            } catch(e: HttpClientResponseException) {
                exceptionThrown = true
            }
            assertTrue(exceptionThrown)
        }
        var accessToken: String? = null
        it("User can login") {
            val creds = UsernamePasswordCredentials("sherlock", "password")
            val request = HttpRequest.POST("/login", creds) (4)

            val rsp: HttpResponse<BearerAccessRefreshToken> = client.toBlocking().exchange(request,
                    BearerAccessRefreshToken::class.java) (5)

            assertEquals(rsp.status()!!, HttpStatus.OK)
            assertNotNull(rsp.body()!!.accessToken)

            accessToken = rsp.body()!!.accessToken (6)
        }
        it("Access / endpoint with appClient") {
            var msg: String = appClient!!.home("Bearer ${accessToken!!}") (7)
            assertTrue(msg == "sherlock")
        }

        afterGroup {
            client.close()
            embeddedServer.close()
        }
    }
})
1 To run the application from a unit test you can use the EmbeddedServer interface
2 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.
3 Retrieve AppClient bean from application context.
4 To login, do a POST request to /login with your credentials as a JSON payload in the body of the request.
5 Micronaut makes it easy to parse JSON into Java objects.
6 Use .body() to retrieve the parsed payload.
7 Supply the JWT to the HTTP Authorization header value to the @Client method.

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.