micronaut logo
java logo

Role-Based Access Control in Micronaut

Updated on June 7, 2024
Photo of Deepu K Sasidharan
Deepu K SasidharanDeveloper Advocate

This Micronaut tutorial will help you learn how to build a Micronaut web app that uses Role-Based Access Control (RBAC) for authorization. The tutorial examples cover the following concepts:

  • How to build a Micronaut web app with Java.
  • How to secure your web app with OAuth 2.0 and OpenID Connect.
  • How to use Auth0 Actions to convert Auth0 roles into Micronaut OIDC roles.
  • How to secure methods with Micronaut's @Secured annotation.

Why Use Micronaut to Build Apps?

Micronaut is a modern, JVM-based framework designed for building modular, easily testable microservices and serverless applications. It focuses on faster start-up time, high throughput, and minimal memory consumption. It is cloud-native by default and includes a built-in testing framework.

OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 framework. It allows third-party applications to verify the identity of the end-user and to obtain basic user profile information.

Role-Based Access Control refers to the idea of assigning permissions to users based on their role within an organization. It offers a simple, manageable approach to access management that is less error-prone than assigning permissions to users individually.

In this guide, you'll learn how to use Java and Micronaut to build a simple app that's secured with OIDC. You'll also learn how to convert your Auth0 roles to Micronaut roles.

Set up a Development Environment

  • Use your favorite text editor or IDE. We recommend using IntelliJ IDEA.
  • Ensure that you have Java 17+ installed in your system. You can easily install it using SDKMAN!.
  • Windows commands in this guide are written for PowerShell.

Create an Auth0 account

If you already have an Auth0 account, you can log in to your tenant and continue to the next step.

Otherwise, sign up for a free Auth0 account.

During the sign-up process, you create something called an Auth0 Tenant, where you configure your use of Auth0.

Once you sign in, Auth0 takes you to the Auth0 Dashboard, where you can configure and manage Auth0 assets, such as applications, APIs, connections, and user profiles.

Set up the Auth0 CLI

If you are not familiar with the Auth0 CLI, you can follow the "Auth0 CLI Basics" lab to learn how to build, manage, and test your Auth0 integrations from the command line.

There are different ways to install the Auth0 CLI, depending on your operating system.

LOADING...

Install the Micronaut CLI

The Micronaut CLI is a command-line tool that helps you create, manage, and build Micronaut applications. You can install it using SDKMAN!.

COMMAND
sdk install micronaut

For more installation options, see the Micronaut documentation.

Create a Micronaut App

To create a new project using the Micronaut CLI, run the following command:

COMMAND
mn create-app demo --features=security-jwt,security-oauth2

This will create a new project with Gradle as the build tool. If you prefer to use Maven, you can add --build=maven to the above command.

If you prefer to use a browser, navigate to https://micronaut.io/launch/ and create a Micronaut project with security-jwt and security-oauth2 features.

If you try to run the app at this point, you will get a runtime error as the OAuth 2.0 details are not configured yet.

Secure Micronaut with OIDC

In a terminal, connect the Auth0 CLI to your Auth0 tenant.

COMMAND
auth0 login
Visit the Access Your Tenant section of the "Auth0 CLI Basics" lab to learn more about the auth0 login command.

Then, run the command below to create an OIDC application:

LOADING...

Copy the domain, client ID, and client secret of your app and paste it into the following input boxes:

When you enter a value in the input fields present on this page, any code snippet that uses such value updates to reflect it. Using the input fields makes copying and pasting code as you follow along easy.

For security, these configuration values are stored in memory and only used locally. They are gone as soon as you refresh the page!

Now, update the application-dev.properties file in the resources directory to configure the OIDC client. Replace the content in the file with the configuration below:

src/main/resources/application-dev.properties
micronaut.security.oauth2.clients.auth0.openid.issuer=https://AUTH0-DOMAIN/
micronaut.security.oauth2.clients.auth0.client-id=AUTH0-CLIENT-ID
micronaut.security.oauth2.clients.auth0.client-secret=${OAUTH_CLIENT_SECRET}
micronaut.security.redirect.unauthorized.url=/oauth/login/auth0
micronaut.security.redirect.login-failure=/oauth/login/auth0
We recommend you DO NOT include the client secret in this file for security reasons. You can use environment variables instead.

Create a HomeController.java class next to Application.java:

LOADING...

Populate it with the following code:

src/main/java/demo/HomeController.java
package demo;
import java.security.Principal;
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.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
@Controller
public class HomeController {
@Get("/")
@Produces(MediaType.TEXT_PLAIN)
@Secured(SecurityRule.IS_AUTHENTICATED)
public String hello(Principal principal) {
return "Hello, " + principal.getName() + "!";
}
}

The @Secured annotation is used to configure secure access. You can also use Jakarta Annotations. The SecurityRule.IS_AUTHENTICATED expression ensures that the user is authenticated before accessing the method.

Navigate to the demo directory, then, run the app with the following command:

GRADLE
MAVEN
OAUTH_CLIENT_SECRET=AUTH0-CLIENT-SECRET ./gradlew run

Open a browser and navigate to http://localhost:8080. Log in with your Auth0 credentials (or sign up as a new user), and you should see your user ID displayed. If you don't have a user in your tenant yet, you can sign up with Google or create a new user with the Auth0 CLI:

COMMAND
auth0 users create
For additional security against replay attacks, you might want to implement a CSRF protection filter.

Use a Login Action to Add Roles

To add a new Administrator role, use the Auth0 CLI:

COMMAND
auth0 roles create --name Administrator --description "Administrators"

Find your user ID with auth0 users search and enter the full email address you used to log in previously.

Assign the role you just created to your user. You must use quotes around the user-id in the command below.

COMMAND
auth0 users roles assign "<user-id>"

Create a Login Action:

COMMAND
auth0 actions create --name "Add Roles" --trigger post-login
You can change the text editor used for editing templates, rules, and actions. Set the environment variable EDITOR to your preferred editor. For example export EDITOR="nano"

When the editor opens, use the following code in the onExecutePostLogin() function. This will set a https://micronaut.example.com/roles claim in both the ID and access token.

ACTION
exports.onExecutePostLogin = async (event, api) => {
const namespace = 'https://micronaut.example.com';
if (event.authorization) {
api.idToken.setCustomClaim('preferred_username', event.user.email);
api.idToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
api.accessToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
}
};

Save the file using your editor. List the available actions with the following command:

COMMAND
auth0 actions list

Save your action ID into an environment variable and deploy the action you just created:

LOADING...

Once the action is deployed, you must attach it to the login flow. You can do this with Auth0 Management API for Actions:

LOADING...

Now the JWT claims will have the roles added to it. To convert this into roles in Micronaut you need to define a custom RoleFinder bean in your application.

Create a CustomRoleFinder.java class next to Application.java:

LOADING...

Populate it with the following code:

src/main/java/demo/CustomRoleFinder.java
package demo;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.security.token.DefaultRolesFinder;
import io.micronaut.security.token.RolesFinder;
import jakarta.inject.Singleton;
import java.util.*;
@Singleton
@Replaces(DefaultRolesFinder.class)
public class CustomRoleFinder implements RolesFinder {
@Override
public @NonNull List<String> resolveRoles(
@Nullable Map<String, Object> attributes) {
var rolesObj = attributes != null ?
attributes.get("https://micronaut.example.com/roles") :
null;
if (rolesObj == null) {
return Collections.emptyList();
}
List<String> rolesFound = new ArrayList<>();
// map values from the role attribute
if (rolesObj instanceof Iterable<?> roles) {
roles.forEach(role -> rolesFound.add(role.toString()));
}
return rolesFound;
}
}

This class will map the https://micronaut.example.com/roles claim to Micronaut roles.

Update your HomeController.java class to add the roles() method returning Micronaut roles in the response:

src/main/java/demo/HomeController.java
package demo;
import java.security.Principal;
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.annotation.Secured;
import io.micronaut.security.authentication.Authentication;
import io.micronaut.security.rules.SecurityRule;
@Controller
public class HomeController {
@Get("/")
@Produces(MediaType.TEXT_PLAIN)
@Secured(SecurityRule.IS_AUTHENTICATED)
public String hello(Principal principal) {
return "Hello, " + principal.getName() + "!";
}
@Get("/roles")
@Produces(MediaType.TEXT_PLAIN)
@Secured(SecurityRule.IS_AUTHENTICATED)
public String roles(Authentication auth) {
return auth.getRoles().toString();
}
}

Restart the application. Navigate again to http://localhost:8080 and authenticate if prompted. Navigate to http://localhost:8080/roles and you should see your roles displayed.

This example proves that your Auth0 roles have been included in your ID token and assigned by Micronaut to the Authentication object.

Secure Methods with Roles

Now that you have Micronaut roles mapped from Auth0 roles, you can use Micronaut's @Secured or @RolesAllowed annotation to secure methods with specific roles.

Secure the roles() method by requiring the Administrator role:

src/main/java/demo/HomeController.java
@Get("/roles")
@Produces(MediaType.TEXT_PLAIN)
@RolesAllowed({"Administrator"})
public String roles(Authentication auth) {
return auth.getRoles().toString();
}

The roles() method requires you to be authenticated and have an Administrator role.

Restart the app. Log in again in a private window, and confirm you can access http://localhost:8080/roles successfully.

This example demonstrates how Micronaut roles can be used for RBAC (Role-Based Access Control).

Verify Roles Are in Your Access Token

To prove that roles have been added to your access token, create a new access token with the Auth0 CLI:

COMMAND
auth0 test token -a https://AUTH0-DOMAIN/api/v2/ -s openid

Select any available client when prompted. You will be prompted to open a browser window and log in with a user credential.

If you'd like to see what's in the access token you created, you can copy and paste it into JWT.io or use JWT UI.

You can also get an access token using the Authorization Code Flow.

Paste the access token value in the following field so that you can use it to test your application:

Then, use it to access your /roles endpoint.

LOADING...

You will receive an HTTP 401 response because the access token is signed using RS256 algorithm and you need to configure JWKS URL in the application so that Micronaut can validate the token.

Update your application-dev.properties and set the JWKS URL for your Auth0 tenant:

src/main/resources/application-dev.properties
micronaut.security.token.jwt.signatures.jwks.auth0.url=https://AUTH0-DOMAIN/.well-known/jwks.json

Now restart the app and send the cURL request to the /roles endpoint again: it should display the roles.

You can also use OAuth scopes to secure your methods.

Create a new method profile() in HomeController.java:

src/main/java/demo/HomeController.java
@Get("/profile")
@Produces(MediaType.TEXT_JSON)
@Secured({"SCOPE_profile"})
public Map<String, Object> profile(Authentication auth) {
return auth.getAttributes();
}

This alone will not work since Micronaut does not process scopes by default. You can enable it by updating the CustomRoleFinder.java class:

src/main/java/demo/CustomRoleFinder.java
package demo;
import io.micronaut.context.annotation.Replaces;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.security.token.DefaultRolesFinder;
import io.micronaut.security.token.RolesFinder;
import jakarta.inject.Singleton;
import java.util.*;
@Singleton
@Replaces(DefaultRolesFinder.class)
public class CustomRoleFinder implements RolesFinder {
@Override
public @NonNull List<String> resolveRoles(
@Nullable Map<String, Object> attributes) {
var rolesObj = attributes != null ?
attributes.get("https://micronaut.example.com/roles") :
null;
var scopesObj = attributes != null ?
attributes.get("scope") :
null;
if (rolesObj == null && scopesObj == null) {
return Collections.emptyList();
}
List<String> rolesFound = new ArrayList<>();
// map values from the role attribute
if (rolesObj instanceof Iterable<?> roles) {
roles.forEach(role -> rolesFound.add(role.toString()));
}
// map values from the scope attribute
if (scopesObj instanceof String scopes) {
var scopeRoles = Arrays.stream(scopes.split(" "))
.map(scope -> "SCOPE_" + scope).toList();
rolesFound.addAll(scopeRoles);
}
return rolesFound;
}
}

This will fetch both roles and scopes from the token and use them for validation.

Restart the app. If you try to access the /profile endpoint, it still won't work:

LOADING...

This is because your access token wasn't created with the profile scope. Create a new access token with this scope:

COMMAND
auth0 test token -a https://AUTH0-DOMAIN/api/v2/ -s openid,profile

Paste the access token value in the following field so that you can use it to test your application:

Try again with the updated access token:

LOADING...

You will see the access token's claims in your terminal. Congratulations!

Stop your Micronaut app using Ctrl+C.

Recap

In this guide, you learned how to build a Micronaut app with Java, secure it with OIDC, and add Auth0 roles so they're converted to Micronaut roles.

COMMAND
mn create-app demo --features=security-jwt,security-oauth2
auth0 apps create \
--name "Micronaut" \
--description "Micronaut Example" \
--type regular \
--callbacks http://localhost:8080/oauth/callback/auth0 \
--logout-urls http://localhost:8080/logout \
--reveal-secrets
auth0 test token -a https://AUTH0-DOMAIN/api/v2/ -s openid
curl localhost:8080/roles -i --header "Authorization: Bearer AUTH0-ACCESS-TOKEN"

Be sure to visit our other Micronaut guides Authentication in Micronaut and Authorization in Micronaut to learn more about Auth0 integration in Micronaut Java applications.