What You’ll Build & Why It Matters
You’ll connect a real Keycloak server to a Next.js app (App Router) via NextAuth.js, protect pages, and ship a smooth SSO-ready login flow. No fluff—just the pieces you actually need in production.
Ship Faster
Cut auth setup by ~30% with a proven stack and copy-pasteable snippets.
Production-Ready
SSO/MFA capable from day one; safer defaults and fewer foot-guns.
Happier Users
Seamless sessions across apps with minimal “please log in again” moments.
Cleaner Ops
Centralized users/roles and fewer manual mistakes in access control.
Keycloak in a Nutshell
Keycloak is an open-source identity and access management server. It handles login, logout, user management, SSO, and MFA using modern standards like OAuth 2.0, OpenID Connect, and SAML. You keep your app lean and let Keycloak do the heavy lifting.
Quick Mental Model
Realm = boundary for users/clients. Client = your app. Provider = protocol bridge (OIDC/SAML). NextAuth.js talks OIDC to Keycloak on your behalf.
Why Teams Pick Keycloak for Auth
- Standards-based SSO across many apps and services.
- Centralized users, groups, and roles—cleaner governance.
- MFA and fine-grained policies without reinventing the wheel.
- Open source, active community, enterprise-ready options.
Pre-Integration Checklist
- Docker installed (for a quick local Keycloak spin-up).
- Next.js 14+ project using the App Router.
- Decide: confidential client (server-side, needs secret) or public (PKCE).
Install & Run Keycloak Quickly
Spin up a local Keycloak using Docker:
1docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin jboss/keycloak
Once up, visit http://localhost:8080/auth
and log into the admin console as admin/admin
. (For production, use the modern quay.io image and secure creds.)
Create Your Realm (The Right Way)
In the admin console, create a realm (e.g., MyRealm
). Enable self-service features you need and set sane password/session policies. Keep things simple; you can refine later.
Realms vs. Clients
A realm holds your users and app configs. Each Next.js app (site, dashboard, API) is typically a separate client.
Configure a Client for Next.js
Create a client (e.g., nextjs-client
) with protocol openid-connect. Set the Root URL to your app (e.g., http://localhost:3000
), and add valid redirect URIs and web origins for NextAuth.js callbacks.
Minimal Client Settings
- Client Protocol: openid-connect
- Client ID: nextjs-client
- Access Type:
- public (PKCE, no client secret), or
- confidential (requires
clientSecret
).
- Valid Redirect URIs: e.g.
http://localhost:3000/api/auth/callback/*
- Web Origins: e.g.
http://localhost:3000
Wire Up NextAuth.js in App Router
NextAuth.js provides a Keycloak provider. In the App Router, you define a route.ts
that exports GET
and POST
handlers.
Install Required Libraries
Install NextAuth.js (and keycloak-js if you also need front-channel flows):
1npm i next-auth keycloak-js
or
1yarn add next-auth keycloak-js
Environment Variables
Create .env.local
:
1KEYCLOAK_URL=http://localhost:8080/auth
2 KEYCLOAK_REALM=MyRealm
3 KEYCLOAK_CLIENT_ID=nextjs-client
4 # If using a confidential client:
5 KEYCLOAK_CLIENT_SECRET=replace-with-generated-secret
6
7 NEXTAUTH_URL=http://localhost:3000
8 NEXTAUTH_SECRET=replace-with-strong-random-string
Generate a Strong Secret
Use OpenSSL (or any secure method): openssl rand -base64 32
Protected Routes & Session Provider
Create app/api/auth/[...nextauth]/route.ts
:
1import NextAuth from 'next-auth'
2 import KeycloakProvider from 'next-auth/providers/keycloak'
3
4 const handler = NextAuth({
5 providers: [
6 KeycloakProvider({
7 clientId: process.env.KEYCLOAK_CLIENT_ID!,
8 clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || '', // omit if public client
9 issuer: `${process.env.KEYCLOAK_URL}/realms/${process.env.KEYCLOAK_REALM}`,
10 }),
11 ],
12 secret: process.env.NEXTAUTH_SECRET,
13 session: { strategy: 'jwt' },
14 callbacks: {
15 async jwt({ token, account, profile }) {
16 // Persist key fields from Keycloak on first sign-in
17 if (account) {
18 token.provider = 'keycloak'
19 }
20 return token
21 },
22 async session({ session, token }) {
23 // Make token fields available on the client
24 session.user = session.user || {}
25 ;(session.user as any).provider = token.provider
26 return session
27 },
28 },
29 })
30
31 export { handler as GET, handler as POST }
This is the App Router pattern—no NextApiRequest
here. Use GET
/POST
exports to wire the auth route.
Add the session provider in app/layout.tsx
:
1import './globals.css'
2 import { SessionProvider } from 'next-auth/react'
3
4 export default function RootLayout({ children }: { children: React.ReactNode }) {
5 return (
6 <html lang="en">
7 <body>
8 <SessionProvider>{children}</SessionProvider>
9 </body>
10 </html>
11 )
12 }
Create a protected page at app/protected/page.tsx
:
1'use client'
2
3 import { signIn, useSession } from 'next-auth/react'
4 import { useEffect } from 'react'
5
6 export default function ProtectedPage() {
7 const { data: session, status } = useSession()
8
9 useEffect(() => {
10 if (status === 'unauthenticated') {
11 signIn() // redirects to Keycloak via NextAuth
12 }
13 }, [status])
14
15 if (status === 'loading') return <div>Loading…</div>
16
17 return (
18 <main className="mx-auto max-w-2xl p-6">
19 <h1 className="mb-2 text-2xl font-semibold">Protected Area</h1>
20 <p className="text-sm text-muted-foreground">
21 Welcome{session?.user?.name ? `, ${session.user.name}` : ''}! You are signed in.
22 </p>
23 </main>
24 )
25 }
Security Hardening & Prod Tips
Recommended Defaults
- Prefer PKCE for public clients.
- For confidential clients, store
KEYCLOAK_CLIENT_SECRET
safely. - Use HTTPS end-to-end and secure cookies.
- Rotate
NEXTAUTH_SECRET
sensibly.
Common Pitfalls
- Mismatched redirect URIs (watch trailing slashes).
- Wrong realm/issuer URL—double-check
/auth/realms/<name>
. - Mixing Pages Router examples in an App Router app.
- Session strategy misconfig (jwt vs database) without intent.
FAQ: Common Issues & Fixes
“Callback URL mismatch” error?
Make sure the Valid Redirect URIs in Keycloak include your NextAuth callback path, e.g. /api/auth/callback/*
.
Do I need a client secret?
Only for confidential clients (server-side). Public clients use PKCE and do not require a secret.
How do I enable MFA?
Configure MFA at the realm level in Keycloak (Authenticator settings/policies). NextAuth consumes the OIDC flow as usual.