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.

🔎 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
:
1 2 3 4 5 6 |
dependencies { implementation("io.micronaut.security:micronaut-security-jwt") } |
Or Maven:
1 2 3 4 5 6 7 |
<dependency> <groupId>io.micronaut.security</groupId> <artifactId>micronaut-security-jwt</artifactId> </dependency> |
⚙️ Configuration (application.yml
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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<AuthenticationResponse> 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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<BearerAccessRefreshToken> 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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
- Login with Admin User
1 2 3 4 5 6 |
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
1 2 3 4 5 6 |
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.