Lock Down Your Micronaut App in Minutes with OAuth2 and JWT Magic

Guarding Your REST API Kingdom with Micronaut's Secret Spices

Lock Down Your Micronaut App in Minutes with OAuth2 and JWT Magic

Implementing OAuth2 and JWT authentication in a Micronaut application is crucial for making sure your REST APIs are secure. Here’s a practical guide to get you set up in no time.

Setting Up Your Micronaut Application

First up, let’s get your Micronaut application off the ground. You can whip up a new Micronaut project using the CLI. Just run this command:

mn create-app my-app --features security-jwt

This command will spin up a new Micronaut application, complete with security and JWT features already enabled. Simple, right?

Enabling Security

Next, you’ll need to enable security in your shiny new Micronaut application. Update your build.gradle file with the necessary dependencies.

dependencies {
    annotationProcessor "io.micronaut:micronaut-security"
    implementation "io.micronaut:micronaut-security"
}

Then, it’s time to configure your application.yml file to turn on these security features.

micronaut:
  security:
    enabled: true

With this setup, any endpoint you try to access will kick back an HTTP Status Unauthorized (401) unless you’re authenticated.

Configuring JWT Authentication

Let’s dive into setting up JWT authentication. JSON Web Tokens (JWT) are a solid pick for their blend of simplicity and security. Micronaut’s got you covered with out-of-the-box support using the Nimbus JOSE + JWT library.

First, specify your JWT settings in the ever-important application.yml.

micronaut:
  security:
    token:
      jwt:
        signatures:
          secret:
            generator:
              secret: "pleaseChangeThisSecretForANewOne"
              jws-algorithm: "HS256"

This setup configures a secret key for signing and verifying your JWTs. Remember to change the secret key to something more secure!

Creating a Login Endpoint

Now, let’s create a login endpoint that issues these JWT tokens. Here’s a nifty example to model your endpoint after:

import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Post
import io.micronaut.security.authentication.UsernamePasswordCredentials
import io.micronaut.security.token.render.BearerAccessRefreshToken

import static io.micronaut.http.HttpHeaders.AUTHORIZATION

@CompileStatic
@Controller("/login")
class LoginController {

    @Post
    BearerAccessRefreshToken login(@Body UsernamePasswordCredentials credentials) {
        // Implement your authentication logic here
        // For example, you can use a service to validate credentials
        // and then generate a JWT token
        return new BearerAccessRefreshToken("your-generated-token", "your-refresh-token")
    }
}

This controller takes care of the login request and will return a JWT token if the entered credentials are good to go.

Securing Endpoints with JWT

Once your login endpoint is operational, you’re ready to lock down other endpoints by requiring a JWT token in the Authorization header. Check out this example showing how to secure an endpoint:

import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Header
import io.micronaut.http.annotation.Secured
import io.micronaut.security.annotation.Secured
import io.micronaut.security.rules.SecurityRule

@CompileStatic
@Controller("/")
@Secured(SecurityRule.IS_AUTHENTICATED)
class HomeController {

    @Get
    @Header(AUTHORIZATION)
    String home(@Header(AUTHORIZATION) String authorization) {
        // This endpoint is only accessible if JWT token is provided
        return "Welcome to the home page!"
    }
}

In this code, the home endpoint is wrapped with the @Secured annotation, meaning you need to be authenticated to get in.

Using OAuth2 for Authentication

OAuth2 is fantastic when you want to integrate with external providers like Google, Okta, or Auth0. Here’s how you can gear up your Micronaut application with OAuth2.

Configuring OAuth2

Start with adding the OAuth2 dependency to your build.gradle file:

dependencies {
    implementation "io.micronaut.configuration:micronaut-security-oauth2"
}

Then, update your application.yml with the OAuth2 settings. Here’s an example using Google as the provider:

micronaut:
  security:
    oauth2:
      enabled: true
      clients:
        google:
          client-id: 'your-client-id'
          client-secret: 'your-client-secret'
          openid:
            issuer: 'https://accounts.google.com'

Swap out the client ID and secret with those provided by your chosen OAuth2 provider.

Handling OAuth2 Callback

With OAuth2, there’s always a callback from the provider. Micronaut handles this gracefully. Configure your callback handling as follows:

micronaut:
  security:
    oauth2:
      clients:
        google:
          openid:
            issuer: 'https://accounts.google.com'
            callback-url: '/oauth/callback/google'

Don’t forget to set up the redirect URL in your OAuth2 provider’s settings. For Google, for example, you’d include http://localhost:8080/oauth/callback/google as an authorized redirect URI.

Testing Your Setup

Of course, you want to make sure everything works perfectly. Writing tests will help you catch any bugs early on. Here’s a test example for JWT authentication using Micronaut’s HTTP client:

import io.micronaut.http.client.annotation.Client
import io.micronaut.security.authentication.UsernamePasswordCredentials
import io.micronaut.security.token.render.BearerAccessRefreshToken
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import spock.lang.Specification

@MicronautTest
class DeclarativeHttpClientWithJwtSpec extends Specification {

    @Inject
    AppClient appClient

    void "Verify JWT authentication works with declarative @Client"() {
        when: 'Login endpoint is called with valid credentials'
        def credentials = new UsernamePasswordCredentials("username", "password")
        def token = appClient.login(credentials)

        then: 'The token is not null'
        token != null

        when: 'The home endpoint is called with the JWT token'
        def response = appClient.home("Bearer " + token.accessToken)

        then: 'The response is successful'
        response == "Welcome to the home page!"
    }
}

@Client("/")
interface AppClient {

    @Post("/login")
    BearerAccessRefreshToken login(@Body UsernamePasswordCredentials credentials)

    @Get
    @Header(AUTHORIZATION)
    String home(@Header(AUTHORIZATION) String authorization)
}

This test ensures your JWT token is issued and used correctly to access a secured endpoint.

Increasing Log Levels for Debugging

If you hit any snags along the way, ramping up your log levels can be a lifesaver. Configure logging in your application.yml file like this:

logger:
  levels:
    io.micronaut.security.authentication: DEBUG

This gives you more detailed logs, super useful for debugging authentication issues.

Conclusion

Rolling out OAuth2 and JWT authentication in a Micronaut application isn’t rocket science. With these straightforward steps, you’ll have a secure setup, ensuring your REST APIs are safeguarded. Always remember to rigorously test to catch any issues early on. Micronaut’s built-in security features make this a breeze. Enjoy secure coding!