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

  1. Client Protocol: openid-connect
  2. Client ID: nextjs-client
  3. Access Type:
    • public (PKCE, no client secret), or
    • confidential (requires clientSecret).
  4. Valid Redirect URIs: e.g. http://localhost:3000/api/auth/callback/*
  5. 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.

Need help with Keycloak ↔ Next.js integration?
I help teams ship secure, production-ready authentication with Keycloak, NextAuth.js, and the App Router—without the rabbit holes. Tell me about your stack and timelines, and we’ll put together a pragmatic plan.