Sep 24, 2025
Next.js + Keycloak + Spring Boot (BFF) - Beginner's Guide

π Next.js + Keycloak + Spring Boot (BFF) - Beginner's Guide
If you've ever tried setting up authentication with Keycloak and NextAuth, you know it can get confusing fast. This guide shows you how to build a setup where tokens never reach the browser - everything stays secure on the server side.
What are we using?
- Frontend: Next.js 15 with NextAuth
- Backend: Spring Boot 3 (validates JWT tokens and roles)
- Identity Provider: Keycloak 24.x
- Pattern: BFF (Backend-for-Frontend)
Project repositories
Why use the BFF pattern?
You might be wondering - why complicate things with BFF? Here's why:
The browser never sees the access token. NextAuth stores the session in HttpOnly cookies (which JavaScript can't read). When a user accesses a protected page, the Next.js server retrieves the token from the session and forwards it to Spring Boot. Spring validates the token and checks permissions.
So the flow is: Browser β Next.js β Spring Boot. Tokens only travel between servers.
What we'll build
We're going to create a simple app with:
- A public homepage (
/) - A protected dashboard (
/app) - Several API routes in Next.js (
/api/me,/api/admin/only) - Spring Boot endpoints that verify roles
Before you start
Make sure you have these installed:
- Docker Desktop
- Node.js 18+ (with npm or yarn)
- Java 21 and Maven
Step 1: Run Keycloak and Mailpit
In the frontend repo (nextauth-keycloak-demo), there's already a docker-compose.yml file that runs both Keycloak and Mailpit together.
Folder structure
web-next/
βββ docker/
β βββ docker-compose.yml
βββ themes/
β βββ my-theme/
β βββ login/...
This setup already includes a custom login theme for Keycloak (in the themes/ folder). The theme is automatically connected to Keycloak through Docker.
Start the containers
cd docker
docker compose up -dNow you can open:
- Keycloak: http://localhost:8080 (login:
admin/admin) - Mailpit (for testing emails): http://localhost:8025
Docker compose uses a kc_data volume, which means your realm, users, and clients will persist even when you stop the container.
Step 2: Create realm, roles, and settings
Create a realm
- Open the Keycloak admin panel
- Click the dropdown next to "master" β Create realm
- Name:
demo
Create roles
- Go to Realm roles β Create role
- Create two roles:
USERandADMIN
Set USER as the default role
This is important - every new user will automatically get the USER role:
- Realm roles β Default roles
- Click Add roles
- Select
USERand add it
(The system will automatically add USER to the default-roles-demo bundle.)
Enable registration, password reset, and email verification
- Go to Realm settings β Login tab
- Enable:
- β User registration
- β Forgot password
- β Verify email
- Save changes
Step 3: Connect Mailpit for sending emails
Keycloak needs to know where to send emails from. Since we're using Mailpit locally, the configuration is straightforward:
- Realm settings β Email tab
- Fill in:
| Field | Value |
|---|---|
| From | admin@demo.local |
| From display name | Keycloak Demo |
| Host | mailpit |
| Port | 1025 |
| Enable SSL | β |
| Enable StartTLS | β |
| Authentication | Disabled |
- Click Test connection
- Open Mailpit (http://localhost:8025) and check if the test email arrived
If you see the email, everything's working!
Step 4: Create an admin user
- Go to Users β Add user
- Fill in:
| Field | Value |
|---|---|
| Username | admin |
| admin@demo.local | |
| First name | Admin |
| Last name | User |
| Email verified | β |
- Click Create
- Go to the Credentials tab
- Click Set password, enter
admin - Turn off the "Temporary" option (so they don't have to change it)
- Save
Now you can log in as admin / admin.
Step 5: Test registration and email verification
Let's verify everything works:
- Open http://localhost:8080/realms/demo/account
- Click Register
- Fill out the form (use an email like
user@demo.local) - Keycloak sends a verification email β open it in Mailpit
- Click the link in the email
- The user is verified and can log in
Check in Keycloak: Users β (select the user) β Details β Email verified: true β
Step 6 (optional): Apply the custom theme
The project already includes a ready-made custom theme for Keycloak login (/themes/my-theme). Docker compose automatically mounts it in the Keycloak container.
To activate it:
- Realm settings β Themes tab
- Under Login theme, select
my-theme - Save
Done! You'll immediately see the new login page design with your colors, logo, and "Back to site" link.
Since caching is disabled in Docker compose, any CSS or template changes will be applied after a page refresh.
Step 7: Run the Spring Boot backend
Set environment variables
KC_ISSUER_URI=http://localhost:8080/realms/demo
ALLOWED_ORIGINS=http://localhost:3000Start the application
mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8082"What does Spring Boot do?
- Validates JWT tokens using the Keycloak issuer
- Extracts roles from
realm_access.rolesand maps them toROLE_*format (e.g.,ROLE_ADMIN) - Returns clean JSON responses for 401/403 errors (good for SSR)
- Has several test endpoints:
GET /api/public/ping(publicly accessible)GET /api/me(requires JWT token)GET /api/admin/only(requires ADMIN role)
Repo: springboot-keycloak-bff
Step 8: Run the Next.js frontend
Create a .env.local file
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=<generate a random 32-byte string>
AUTH_KEYCLOAK_ISSUER=http://localhost:8080/realms/demo
AUTH_KEYCLOAK_ID=next-app
AUTH_KEYCLOAK_SECRET=<client secret from Keycloak>
BACKEND_URL=http://localhost:8082Note: You get the client secret in Keycloak under Clients β
next-appβ Credentials tab.
How does NextAuth work?
The NextAuth provider uses Keycloak with PKCE flow:
Keycloak({
issuer: process.env.AUTH_KEYCLOAK_ISSUER,
clientId: process.env.AUTH_KEYCLOAK_ID,
clientSecret: process.env.AUTH_KEYCLOAK_SECRET,
authorization: { params: { scope: "openid profile email" } },
checks: ["pkce", "state"],
})Key features in the project
- Middleware protects
/app/*and/api/*routes - API routes (
/api/me,/api/admin/only,/api/update) forward the access token to Spring Boot - Register page uses
signIn("keycloak", { callbackUrl: "/app" }, { kc_action: "register" }) - Logout calls
/api/auth/kc-logoutwhich revokes the refresh token before logging out
Repo: nextauth-keycloak-demo
Step 9: Test the entire application
Now it's time to try everything together:
- Open http://localhost:3000/app
- You'll be redirected to Keycloak login
- Register a new user
- Verify the email through Mailpit
- Log in and see your dashboard with claims
- In Keycloak, add the
ADMINrole to yourself - Test access to the
/api/admin/onlyendpoint
Common issues and solutions
PKCE mismatch error
Problem: You see a PKCE-related error in the console or logs.
Solution: Check that the Keycloak client has PKCE required: S256 enabled and that NextAuth has checks: ["pkce", "state"].
403 on /api/admin/only
Problem: User can't access the admin route.
Solution: Verify that the token contains the ADMIN role in realm_access.roles. Check the token at https://jwt.io or in the app's dashboard.
Theme not applying
Problem: You still see the default Keycloak theme.
Solution: Go to Realm settings β Themes and select my-theme for Login theme. Save and refresh the page.
Data is lost when Docker stops
Problem: The realm and users disappear when you stop the container.
Solution: Docker compose already uses the kc_data volume which persists all data. Check that the volume is properly mounted in docker-compose.yml.
No "Back to site" link on login page
Problem: You don't see the link that leads back to the homepage.
Solution: Check the themes/my-theme/login/theme.properties file and confirm that the backToSiteUrl value exists.
Wrap-up
Congratulations! You've built a complete authentication system with:
- Next.js as BFF (authentication + proxy, tokens don't reach the browser)
- Spring Boot as a JWT resource server with role-based endpoints
- Keycloak for identity management (with registration, email verification, default USER role, and custom theme)
- Mailpit for local email testing
You can use this setup as a foundation for production applications. All you need to do is replace Mailpit with a real SMTP service (like SendGrid or AWS SES) and deploy everything to a server.
Happy coding! π
