← All posts
·7 min read

Claude Code with Keycloak: Identity for AI Workflows

Claude CodeKeycloakAuthentication

Keycloak is the open-source identity provider that most teams reach for once they need real SSO, federated identity, fine-grained roles, or anything that an off-the-shelf auth library cannot give them. The catch is that Keycloak is large. There are realms, clients, roles, scopes, identity providers, authentication flows, and a admin console with hundreds of toggles. Claude Code is uniquely good at navigating exactly this kind of high-configuration system because it can read your existing setup and make precise edits.

This post is a practical guide to using Claude Code with Keycloak: how to scaffold a working setup, how to integrate it into your application, and the patterns that hold up in production.

Why Keycloak for AI workflows

You can get away with magic links and a single user table when you are shipping a side project. The moment you have multiple apps, internal tools, contractors who need scoped access, or a customer enterprise asking for SAML, you need a real IdP. The options narrow fast: Auth0 and Okta if you want managed, Keycloak if you want self-hosted and free.

For AI workflows specifically, Keycloak earns its place when:

  • You have multiple Claude Code agents or services that need to authenticate to your APIs.
  • You want one identity for users across a web app, a CLI, and an internal admin.
  • You need to federate identity from a customer's Active Directory or Google Workspace.
  • You want token-based auth with short-lived access tokens and refresh rotation.

If those needs are not yet pressing, Better Auth or Clerk are friendlier. Keycloak is the right choice when you need control.

Running Keycloak locally

Claude Code can spin up a local Keycloak in a single command via Docker. Add this to your docker-compose.yml:

services:
  keycloak:
    image: quay.io/keycloak/keycloak:25.0
    command: start-dev
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KC_DB: dev-file
    ports:
      - "8080:8080"
    volumes:
      - keycloak-data:/opt/keycloak/data

volumes:
  keycloak-data:
docker compose up -d keycloak

In a few seconds you have a Keycloak admin console at http://localhost:8080. The admin user is admin / admin. Production should use a managed database (Postgres) and real credentials, but for dev this is enough.

Ask Claude Code to also write a keycloak.env file with the bootstrap config so the realm and client get created automatically on first run. That keeps the setup reproducible.

Realm, client, and roles

A Keycloak realm is an isolated namespace for users, groups, roles, and clients. A client is an application or service that authenticates against the realm.

For a typical setup with a web app and an API:

  1. Create a realm called production (or your product name).
  2. Create a client called web-app with type OpenID Connect, public access, and the redirect URIs you need.
  3. Create a client called api with type OpenID Connect, confidential access, and service-account enabled if your API itself needs to call other services.
  4. Define realm-level roles: admin, editor, viewer. Or whatever your model is.

Claude Code can drive this through the Keycloak Admin REST API instead of clicking through the console. The pattern that works:

#!/usr/bin/env bash
# scripts/bootstrap-keycloak.sh
set -euo pipefail

KC=http://localhost:8080
ADMIN_TOKEN=$(curl -sS -X POST "$KC/realms/master/protocol/openid-connect/token" \
  -d "client_id=admin-cli" \
  -d "username=admin" \
  -d "password=admin" \
  -d "grant_type=password" \
  | jq -r .access_token)

curl -sS -X POST "$KC/admin/realms" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"realm": "production", "enabled": true}'

curl -sS -X POST "$KC/admin/realms/production/clients" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "web-app",
    "publicClient": true,
    "redirectUris": ["http://localhost:3000/*"],
    "webOrigins": ["http://localhost:3000"]
  }'

Run this once. The realm, clients, and roles are now defined. Commit the script. New developers get the same setup with one command.

Integrating with a Next.js app

The cleanest way to use Keycloak from a Next.js app is via NextAuth. Claude Code can wire this up directly:

pnpm add next-auth
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"
import KeycloakProvider from "next-auth/providers/keycloak"

const handler = NextAuth({
  providers: [
    KeycloakProvider({
      clientId: process.env.KEYCLOAK_CLIENT_ID!,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
      issuer: process.env.KEYCLOAK_ISSUER!, // e.g. http://localhost:8080/realms/production
    }),
  ],
  callbacks: {
    async session({ session, token }) {
      session.accessToken = token.accessToken as string
      session.roles = token.roles as string[]
      return session
    },
    async jwt({ token, account, profile }) {
      if (account) {
        token.accessToken = account.access_token
        token.roles = (profile as any)?.realm_access?.roles ?? []
      }
      return token
    },
  },
})

export { handler as GET, handler as POST }

The provider does the OAuth dance. Roles come through as a string array in the JWT and become available on the session. For a deeper look at NextAuth patterns, see our Claude Code NextAuth guide.

Protecting your API

The web app handles the user flow. Your API needs to verify tokens it receives. The right way is JWT validation against Keycloak's JWKS endpoint.

// lib/auth.ts
import { jwtVerify, createRemoteJWKSet } from "jose"

const JWKS = createRemoteJWKSet(
  new URL(`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/certs`)
)

export async function verifyToken(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: process.env.KEYCLOAK_ISSUER,
    audience: "api",
  })
  return payload
}

Then in your route handlers:

export async function GET(req: Request) {
  const auth = req.headers.get("authorization")
  if (!auth?.startsWith("Bearer ")) return new Response("Unauthorised", { status: 401 })

  try {
    const payload = await verifyToken(auth.slice(7))
    const roles = (payload.realm_access as any)?.roles ?? []
    if (!roles.includes("editor")) return new Response("Forbidden", { status: 403 })
    // proceed
  } catch {
    return new Response("Unauthorised", { status: 401 })
  }
}

This pattern works in any framework. Express, FastAPI, Spring Boot, Go: they all have a JWT library and an HTTP middleware layer. Ask Claude Code to translate it to your stack.

Get Claudify. The official Claude Code starter kit, configured for auth-heavy production stacks.

Service-to-service authentication

If you have backend services that call each other (Claude Code agents calling APIs, microservices calling microservices), you want machine-to-machine auth, not user tokens. Keycloak does this via service accounts.

Enable "Service accounts" on a confidential client. Then:

TOKEN=$(curl -sS -X POST "$KC/realms/production/protocol/openid-connect/token" \
  -d "client_id=worker-service" \
  -d "client_secret=$WORKER_SECRET" \
  -d "grant_type=client_credentials" \
  | jq -r .access_token)

curl -sS -H "Authorization: Bearer $TOKEN" https://api.example.com/internal/job

The access token is short-lived (default 5 minutes). Cache it in the calling service. Refresh just before expiry. Claude Code can write the caching layer for whatever language you are using.

Federating to Google, Microsoft, or SAML

The point of Keycloak is that you can plug in external identity providers without touching your application code. To add Google as an IdP:

  1. Admin Console > Identity Providers > Add provider > Google.
  2. Drop in the client ID and secret from Google Cloud Console.
  3. Save.

Users now have a "Sign in with Google" button on the Keycloak login page. Your app gets the same Keycloak token regardless of which IdP the user used. SAML works the same way for enterprise SSO. Your app does not change.

This is the core reason Keycloak wins for enterprise. The customer says "we need SAML against our Okta tenant" and you flip a switch instead of shipping code.

Production patterns

A few things that matter for production Keycloak deployments:

Use Postgres, not the dev DB. The dev-file database is for local only. Production needs a real RDBMS with backups.

Run multiple replicas. Keycloak is stateless if you use a shared database and Infinispan in distributed mode. Three replicas behind a load balancer is the standard pattern.

Rotate signing keys. Keycloak rotates RSA keys for token signing. Make sure your apps use JWKS lookup (not hardcoded keys) so rotation works without restarts.

Realm export and import. For disaster recovery, export your realm config regularly with kc.sh export. Treat the realm config as code. Commit the exported JSON to a repo.

Theme the login page. The default login page is bare. A custom theme branded to your product feels much more polished. Claude Code can fork the base theme and customise it without breaking the auth flow. See our Claude Code Tailwind guide for how to use Tailwind in non-React contexts.

Common pitfalls

Mismatched redirect URIs. The most common Keycloak error is redirect_uri did not match. Wildcard support is limited. Set the exact URI, or use a wildcard suffix like https://app.example.com/*. Match scheme, host, and port precisely.

Token expiry surprises. Default access token lifespan is 5 minutes. If your frontend does not refresh, users get logged out mid-session. Either extend the lifespan in the realm settings or implement silent refresh in your app.

Cross-origin issues. Keycloak enforces Web Origins on public clients. If your frontend is on https://app.example.com and Keycloak is on https://auth.example.com, add the frontend origin to the client's Web Origins list.

Performance under load. A single Keycloak instance can serve thousands of token validations per second if you use JWKS validation in the app (no round trip to Keycloak per request). If your app calls the Keycloak userinfo endpoint on every request, you are doing it wrong. Validate the JWT locally.

Conclusion

Keycloak is the right tool when you have outgrown the SaaS auth providers but do not want to write your own IdP. Claude Code is the right partner because it can drive the Keycloak Admin API, generate the integration code for any stack, and keep the realm config in source control.

If you want a Claude Code project that already understands auth patterns, with example integrations and a working CLAUDE.md, that is what Claudify packages.

Get Claudify. The official Claude Code starter kit for developers shipping production identity and auth.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir Claudify - Featured on Startup Fame