📚 Full tutorial: Read the step-by-step guide here →
https://natasatm.netlify.app/articles/nextjs-keycloak-spring-boot-bff
This repository demonstrates a Spring Boot 3 Backend-for-Frontend (BFF) secured with Keycloak (OIDC).
It exposes protected API routes that validate JWT tokens issued by Keycloak and enforce role-based access control.
- 🔐 JWT validation against Keycloak issuer
- 👥 Role-based endpoints (
USERandADMIN) - 🚧 Public vs protected API routes
- 🤝 Designed to pair with the Next.js + NextAuth frontend
- Overview
- Tech stack
- Project structure
- Quick start
- Environment variables
- API endpoints
- How it works
- Deployment notes
- Useful links
- License
This BFF sits between your frontend and downstream services.
Tokens are validated server-side against Keycloak. Role-based claims are read from realm_access.roles.
Example flow:
- User logs in with Keycloak through the frontend
- Frontend calls BFF routes (
/api/me,/api/admin/only, etc.) - BFF validates the token and either allows or denies access
- Java 21
- Spring Boot 3.x
- Spring Security (OAuth2 Resource Server)
- Maven build system
src/
main/
java/com/natasatm/authdemo/
api/
AdminController.java # ADMIN role protected endpoints
MeController.java # User info + public ping endpoint
config/
JwtRolesConfig.java # JWT role extraction configuration
SecurityConfig.java # Security and CORS configuration
AuthDemoApiApplication.java # Main Spring Boot application
resources/
application.yml # Application configuration
- Java 21 and Maven
- Keycloak running (see frontend repo for Docker setup)
Note: Keycloak and Mailpit are configured in the frontend repository.
Run the Docker Compose setup from there first before starting this backend.
git clone https://github.com/NatasaTM/springboot-keycloak-bff.git
cd springboot-keycloak-bffexport KC_ISSUER_URI=http://localhost:8080/realms/demo
export ALLOWED_ORIGINS=http://localhost:3000Or create a .env file if you're using a tool like direnv.
mvn spring-boot:runThe API will be available at http://localhost:8082.
| Variable | Description | Example |
|---|---|---|
KC_ISSUER_URI |
Keycloak realm issuer URL | http://localhost:8080/realms/demo |
ALLOWED_ORIGINS |
CORS allowed origins (comma-separated) | http://localhost:3000 |
GET /api/public/ping
No authentication required
Returns:{"status": "ok"}
-
GET /api/me
Returns user information and roles
Example response:{ "sub": "f12c3d45-6789-0123-4567-89abcdef0123", "email": "demo@demo.test", "preferred_username": "demo@demo.test", "issuedAt": "2024-01-15T10:30:00Z", "expiresAt": "2024-01-15T11:30:00Z", "roles": ["USER", "ADMIN"] } -
GET /api/admin/only
RequiresADMINrole
Returns:"ok - admin"
GET /actuator/health- Application health statusGET /actuator/info- Application information
Spring Security validates incoming JWT tokens against the Keycloak issuer configured in KC_ISSUER_URI. The validation includes:
- Token signature verification
- Token expiration check
- Issuer validation
Roles are extracted from the realm_access.roles claim in the JWT token and mapped to Spring Security authorities with the ROLE_ prefix:
{
"realm_access": {
"roles": ["USER", "ADMIN"]
}
}These become ROLE_USER and ROLE_ADMIN in Spring Security.
The security configuration (SecurityConfig.java) defines:
- Public endpoints that don't require authentication
- CORS configuration for frontend communication
- JWT token validation settings
- Role-based access control rules
- Make sure Keycloak is running (use Docker Compose from the frontend repo)
- Configure environment variables
- Run with
mvn spring-boot:run
When deploying to production:
- Use HTTPS for both Keycloak and this backend
- Update
KC_ISSUER_URIto point to your production Keycloak instance - Configure
ALLOWED_ORIGINSto match your production frontend URL - Use environment variables provided by your hosting platform (Render, Railway, Heroku, etc.)
- Consider using an external database for Keycloak instead of the development setup
- Enable proper logging and monitoring
- Create a new Web Service
- Connect your GitHub repository
- Set build command:
mvn clean package -DskipTests - Set start command:
java -jar target/*.jar - Add environment variables in the Render dashboard
- Deploy
- Full tutorial: https://natasatm.netlify.app/articles/nextjs-keycloak-spring-boot-bff
- Frontend companion (Next.js): https://github.com/NatasaTM/nextauth-keycloak-demo
- Spring Boot documentation
- Spring Security OAuth2 Resource Server
- Keycloak documentation
- Check that the JWT token is valid and not expired
- Verify
KC_ISSUER_URImatches your Keycloak realm URL - Ensure the token was issued by the correct Keycloak instance
- Verify the user has the required role (
USERorADMIN) - Check that roles are correctly configured in Keycloak
- Inspect the JWT token at https://jwt.io to see the
realm_access.rolesclaim
- Update
ALLOWED_ORIGINSto include your frontend URL - Make sure the frontend URL matches exactly (including protocol and port)
MIT © 2025 Natasa Todorov Markovic
Feel free to fork and adapt.