Role-Based Access Control in Micronaut: Secure Your Endpoints the Right Way

In any application dealing with users, data privacy, or critical workflows, Role-Based Access Control (RBAC) is essential. With Micronaut, implementing RBAC is clean and powerful thanks to its built-in security framework and support for annotations.

This guide will walk you through implementing Role-Based Access Control in Micronaut, including custom roles, securing endpoints, and integrating with JWT authentication. We’ll use the package com.kscodes.micronaut.security for all the code examples.

Role-Based Access Control in Micronaut

🔎 What is Role-Based Access Control (RBAC)?

RBAC is a security mechanism where access permissions are granted based on the role assigned to a user. For example:

  • ROLE_USER: Can access their own data.
  • ROLE_ADMIN: Can manage all users.
  • ROLE_MANAGER: Can manage specific segments.

In Micronaut, roles are usually carried within JWT tokens and validated automatically using annotations like @Secured.

🛠 Project Setup

1. Add Required Dependencies

In your build.gradle:


dependencies {
    implementation("io.micronaut.security:micronaut-security-jwt")
}

Or Maven:



  io.micronaut.security
  micronaut-security-jwt


⚙️ Configuration (application.yml)


micronaut:
  application:
    name: rbac-demo
  security:
    enabled: true
    token:
      jwt:
        signatures:
          secret:
            generator:
              secret: "kscodes-role-based-key"
    authentication: bearer

👤 Creating a Custom Authentication Provider

File: com.kscodes.micronaut.security.RoleAuthProvider.java


package com.kscodes.micronaut.security;

import io.micronaut.security.authentication.*;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import java.util.List;

@Singleton
public class RoleAuthProvider implements AuthenticationProvider {

    @Override
    public Publisher authenticate(AuthenticationRequest request) {
        String username = request.getIdentity().toString();
        String password = request.getSecret().toString();

        if ("admin".equals(username) && "admin123".equals(password)) {
            return Mono.just(AuthenticationResponse.success(username, List.of("ROLE_ADMIN")));
        } else if ("user".equals(username) && "user123".equals(password)) {
            return Mono.just(AuthenticationResponse.success(username, List.of("ROLE_USER")));
        }

        return Mono.just(new AuthenticationFailed());
    }
}

🔑 Login Endpoint to Issue JWT

File: com.kscodes.micronaut.security.LoginController.java


package com.kscodes.micronaut.security;

import io.micronaut.http.annotation.*;
import io.micronaut.security.authentication.UsernamePasswordCredentials;
import io.micronaut.security.token.jwt.render.BearerAccessRefreshToken;
import io.micronaut.security.authentication.Authentication;
import io.micronaut.security.token.jwt.generator.JwtGeneratorConfiguration;
import io.micronaut.security.token.jwt.generator.JwtTokenGenerator;
import jakarta.inject.Inject;
import reactor.core.publisher.Mono;

@Controller("/auth")
public class LoginController {

    @Inject
    JwtTokenGenerator tokenGenerator;

    @Post("/login")
    public Mono login(@Body UsernamePasswordCredentials credentials) {
        Authentication user = Authentication.build(credentials.getUsername(), List.of());
        return Mono.just(
                new BearerAccessRefreshToken("RBAC-Demo", credentials.getUsername(), null,
                        tokenGenerator.generateToken(user).orElse(""), null));
    }
}

🔒 Securing Endpoints by Role

File: com.kscodes.micronaut.security.DashboardController.java


package com.kscodes.micronaut.security;

import io.micronaut.http.annotation.*;
import io.micronaut.security.annotation.Secured;

@Controller("/dashboard")
public class DashboardController {

    @Secured("ROLE_ADMIN")
    @Get("/admin")
    public String adminDashboard() {
        return "Welcome to Admin Dashboard";
    }

    @Secured("ROLE_USER")
    @Get("/user")
    public String userDashboard() {
        return "Welcome to User Dashboard";
    }

    @Secured({"ROLE_ADMIN", "ROLE_USER"})
    @Get("/common")
    public String commonDashboard() {
        return "Accessible to both Admin and User roles";
    }
}

🧪 Testing RBAC in Micronaut

  1. Login with Admin User

curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin123"}'

Access /dashboard/admin with token

2. Login with Regular User


curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "user", "password": "user123"}'

3. Access /dashboard/user with user token

4. Try accessing /dashboard/admin with user token – should return 403 Forbidden

⚡ Advanced Tips

  • Store roles in the JWT claims for efficiency.
  • Use @Secured at method or class level for fine-grained control.
  • For dynamic access logic, implement SecurityRule.
  • Avoid hardcoding roles—use a database or external provider (e.g., OAuth2).

📚 External References

✅ Conclusion

Role-Based Access Control in Micronaut enables you to build robust, secure APIs with minimal boilerplate. Using the @Secured annotation and Micronaut’s JWT integration, you can enforce strict access rules based on user roles in a clean, declarative way.

With this guide, you’ve learned how to define roles, secure endpoints, generate JWTs, and apply real-world access control patterns using the com.kscodes.micronaut.security package.

Start small, scale securely—and let RBAC do the heavy lifting.