Sep 24, 2025

Next.js + Keycloak + Spring Boot (BFF) - Beginner's Guide

authsecuritynextjsspring-bootkeycloakjwtbff
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 -d

Now you can open:

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

  1. Open the Keycloak admin panel
  2. Click the dropdown next to "master" β†’ Create realm
  3. Name: demo

Create roles

  1. Go to Realm roles β†’ Create role
  2. Create two roles: USER and ADMIN

Set USER as the default role

This is important - every new user will automatically get the USER role:

  1. Realm roles β†’ Default roles
  2. Click Add roles
  3. Select USER and add it

(The system will automatically add USER to the default-roles-demo bundle.)

Enable registration, password reset, and email verification

  1. Go to Realm settings β†’ Login tab
  2. Enable:
    • βœ… User registration
    • βœ… Forgot password
    • βœ… Verify email
  3. 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:

  1. Realm settings β†’ Email tab
  2. Fill in:
FieldValue
Fromadmin@demo.local
From display nameKeycloak Demo
Hostmailpit
Port1025
Enable SSL❌
Enable StartTLS❌
AuthenticationDisabled
  1. Click Test connection
  2. 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

  1. Go to Users β†’ Add user
  2. Fill in:
FieldValue
Usernameadmin
Emailadmin@demo.local
First nameAdmin
Last nameUser
Email verifiedβœ…
  1. Click Create
  2. Go to the Credentials tab
  3. Click Set password, enter admin
  4. Turn off the "Temporary" option (so they don't have to change it)
  5. Save

Now you can log in as admin / admin.


Step 5: Test registration and email verification

Let's verify everything works:

  1. Open http://localhost:8080/realms/demo/account
  2. Click Register
  3. Fill out the form (use an email like user@demo.local)
  4. Keycloak sends a verification email β†’ open it in Mailpit
  5. Click the link in the email
  6. 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:

  1. Realm settings β†’ Themes tab
  2. Under Login theme, select my-theme
  3. 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:3000

Start 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.roles and maps them to ROLE_* 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:8082

Note: 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-logout which 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:

  1. Open http://localhost:3000/app
  2. You'll be redirected to Keycloak login
  3. Register a new user
  4. Verify the email through Mailpit
  5. Log in and see your dashboard with claims
  6. In Keycloak, add the ADMIN role to yourself
  7. Test access to the /api/admin/only endpoint

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.

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! πŸš€