| name | keycloak |
| description | Configure Keycloak realm/client settings, integrate Auth.js v5 OIDC in the web portal (project.json → mainApp), set Keycloak Helm values (init/full stage), recover bootstrap admin, or debug KC_HOSTNAME/issuer mismatches. Do not use for Helm chart templating (use helm-chart-expert), GCP firewall rules (use kubernetes-deployment), or database migrations (use migration-check). |
| version | 1.0.0 |
| owner | swarmery-infra |
| allowed-tools | Read, Bash, Write, Edit, Grep, Glob, WebFetch, WebSearch |
| docs | {"status":"reviewed","source_sha":"47cf71e1a20f","updated":"2026-08-06T00:00:00.000Z"} |
Purpose
Provide Keycloak identity and access management patterns for the platform. Cover realm setup, client configuration for Auth.js v5 in the web portal repo (project.json → mainApp), two-stage Helm deployment (init/full), bootstrap admin recovery, issuer mismatch fixes, and NetworkPolicy requirements. For Helm chart template authoring beyond Keycloak values, defer to helm-chart-expert. For GCP firewall or ingress debugging, defer to kubernetes-deployment.
When to use
- Configuring the
<keycloak-realm> realm, clients, roles, or groups in Keycloak
- Integrating Auth.js v5 with Keycloak OIDC in the web portal
- Setting up Keycloak Helm values (init stage or full stage with ingress)
- Debugging Auth.js
error=Configuration or issuer mismatch (iss claim)
- Recovering from bootstrap admin failures (existing database, OOM-kill)
- Configuring NetworkPolicy for ingress-to-Keycloak traffic
When NOT to use
- Authoring Helm chart templates or
_helpers.tpl -- use helm-chart-expert
- Debugging GCP firewall rules or minikube tunnel -- use
kubernetes-deployment
- Checking migration safety -- use
migration-check
- Detecting IaC config drift -- use
infrastructure-as-code
Required environment
- Runtime:
.claude/skills/keycloak/SKILL.md
- Tools:
kubectl, helm, bash
- Keycloak version: 26.x (codecentric/keycloakx Helm chart)
- Setup script: the infrastructure repo's
files/keycloak/setup-keycloak.sh (or the project's equivalent)
- Namespace: environment-specific (
<infra-namespace>)
Inputs
operation: enum -- one of: realm-setup, auth-integration, debug-auth, bootstrap-recovery, helm-values
environment: string -- target environment (localdev, <envAlias>, prod)
symptom: string (optional) -- error message or behavior being debugged
Outputs
- Format: configuration YAML, Auth.js TypeScript code, or a diagnostic command sequence
- Length budget: max 150 lines per response; for multi-operation tasks, produce one section per operation
- For
debug-auth: ordered diagnosis steps with specific kubectl/curl commands
- For
bootstrap-recovery: step-by-step recovery procedure with safety warnings
Procedure
-
Identify operation and environment -- Confirm which Keycloak operation is needed and which environment.
Checkpoint: Operation type determined.
-
Check prerequisites -- Verify Keycloak pod is running (kubectl get pods -n $NAMESPACE | grep keycloak). For auth debugging, verify both internal DNS and ingress are reachable.
Checkpoint: Keycloak pod status confirmed.
-
Apply configuration -- Use the appropriate pattern (Helm values, setup script, or Auth.js config).
Checkpoint: Config applied or code change suggested.
-
Verify -- For auth integration, confirm the OIDC discovery endpoint is reachable and the issuer URL matches AUTH_KEYCLOAK_ISSUER. Run: curl <issuer>/.well-known/openid-configuration and verify the JSON issuer field.
Checkpoint: Discovery endpoint returns valid JSON with matching issuer.
Self-check
Common mistakes
- DO NOT hardcode admin passwords in values examples -- use
$KC_ADMIN_PASSWORD placeholder with a # LOCAL DEV ONLY comment
- DO NOT use
name: ingress-nginx as a namespace selector label -- minikube addon does not add it; use kubernetes.io/metadata.name: ingress-nginx
- DO NOT run
/opt/keycloak/bin/kc.sh bootstrap-admin user inside a running pod -- starts a second JVM, causes OOM-kill at 1Gi memory limit
- DO NOT assume
KC_BOOTSTRAP_ADMIN_* vars work on an existing database -- they only take effect on first startup with an empty auth database
- DO NOT rely on client-side auth checks alone -- always re-verify in the route handler or server action
- DO NOT use
NEXTAUTH_URL -- this is Auth.js v4 naming. In Auth.js v5, the env var is AUTH_URL
Escalation
- STOP when: the Keycloak pod is in CrashLoopBackOff and logs show a database connection error (may need PostgreSQL debugging)
- STOP when: the user wants to modify production Keycloak security settings
- STOP when: the issuer mismatch involves a certificate trust chain issue
Examples
Example: Two-stage Helm deployment
Stage 1 -- Init (bootstrap without ingress):
keycloak:
enabled: true
command: ["/opt/keycloak/bin/kc.sh"]
args: ["start-dev"]
database:
vendor: postgres
hostname: <infra-release>-postgresql.$NAMESPACE.svc.cluster.local
port: 5432
database: user_access
existingSecret: <infra-release>-postgresql
existingSecretKey: postgres-password
extraEnv: |
- name: KC_BOOTSTRAP_ADMIN_USERNAME
value: admin
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
value: "$KC_ADMIN_PASSWORD" # LOCAL DEV ONLY -- never use in staging/prod
Stage 2 -- Full (add ingress + hostname):
keycloak:
enabled: true
extraEnv: |
- name: KC_HOSTNAME
value: "keycloak.<localdev-host>"
- name: KC_HOSTNAME_STRICT
value: "false"
ingress:
enabled: true
ingressClassName: nginx
rules:
- host: "keycloak.<localdev-host>"
paths:
- path: /
pathType: Prefix
Example: Auth.js v5 integration in the web portal
import NextAuth from "next-auth";
import Keycloak from "next-auth/providers/keycloak";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Keycloak({
clientId: process.env.KEYCLOAK_CLIENT_ID!,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
issuer: process.env.KEYCLOAK_ISSUER!,
}),
],
callbacks: {
async jwt({ token, account, profile }) {
if (account) {
token.accessToken = account.access_token;
token.roles = (profile as { realm_access?: { roles?: string[] } })
?.realm_access?.roles ?? [];
}
return token;
},
async session({ session, token }) {
session.accessToken = token.accessToken as string | undefined;
session.roles = (token.roles as [] | ) ?? [];
session;
},
},
});
Required env vars: AUTH_SECRET, AUTH_URL, KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET, KEYCLOAK_ISSUER.
Note: AUTH_URL replaces the Auth.js v4 NEXTAUTH_URL. Do not use NEXTAUTH_URL in Auth.js v5 projects.
Example: Diagnosing issuer mismatch (error=Configuration)
Symptom: Auth.js callback returns error=Configuration. Browser shows redirect loop.
Cause: the web portal pod exchanges tokens via internal cluster DNS (<infra-release>-keycloak-http.$NAMESPACE.svc.cluster.local). Keycloak embeds that internal URL as the iss claim. Auth.js rejects it because iss does not match KEYCLOAK_ISSUER (the public HTTPS URL).
Fix: Add KC_HOSTNAME_URL to Keycloak env vars:
keycloak:
extraEnv: |
- name: KC_HOSTNAME
value: "keycloak.staging.example.com"
- name: KC_HOSTNAME_URL
value: "https://keycloak.staging.example.com"
- name: KC_HOSTNAME_STRICT
value: "true"
KC_HOSTNAME controls request routing and admin console hostname
KC_HOSTNAME_URL controls the full base URL in JWT iss claims and OIDC discovery
- Both are needed when internal and external URLs differ
Example: Bootstrap admin recovery (existing database)
KC_BOOTSTRAP_ADMIN_* vars only take effect on first startup with an empty database.
Recovery procedure:
kubectl scale deployment <infra-release>-keycloak -n "$NAMESPACE" --replicas=0
kubectl exec -n "$NAMESPACE" <infra-release>-postgresql-0 -- \
psql -U postgres -c "DROP DATABASE user_access;"
kubectl exec -n "$NAMESPACE" <infra-release>-postgresql-0 -- \
psql -U postgres -c "CREATE DATABASE user_access;"
kubectl scale deployment <infra-release>-keycloak -n "$NAMESPACE" --replicas=1
./files/keycloak/setup-keycloak.sh local
Failure modes
| Mode | Symptom | Fix |
|---|
| error=Configuration | Redirect loop or error page after login | Decode JWT, check iss vs KEYCLOAK_ISSUER; add KC_HOSTNAME_URL |
| Bootstrap admin fails | Keycloak starts but admin login fails, no error in logs | Check if the auth DB has existing data; drop/recreate DB |
| NetworkPolicy blocks ingress | Keycloak unreachable via ingress but port-forward works | Use kubernetes.io/metadata.name: ingress-nginx selector |
| TLS 308 redirect timeout | ERR_CONNECTION_TIMED_OUT on HTTPS, HTTP works | Check GCP firewall for allow-https rule; see kubernetes-deployment |
Related skills
helm-chart-expert -- defer for Helm template authoring; compose when Keycloak values need template-level changes
kubernetes-deployment -- defer for GCP firewall, minikube tunnel, and ingress debugging
infrastructure-as-code -- defer for drift detection; compose when Keycloak Helm values override was applied manually
migration-check -- no direct overlap; Keycloak manages its own database schema
How to use
What it does
This skill gives you working Keycloak patterns for a Kubernetes platform: realm and client setup, Auth.js v5 OIDC wiring in the web portal, two-stage Helm values (init then full with ingress), bootstrap admin recovery, and the KC_HOSTNAME vs KC_HOSTNAME_URL distinction that causes most issuer mismatches. It knows the traps — OOM-killed bootstrap commands, NetworkPolicy selectors that silently drop ingress traffic, v4 env var names in a v5 project.
When to use it
- Login fails with
error=Configuration or a redirect loop, and you suspect the iss claim.
- You are adding Keycloak sign-in to the web portal with Auth.js v5 and need the provider, callbacks, and env vars.
- You need Keycloak Helm values for a fresh environment, either the init stage or the full stage with ingress.
- The admin account no longer works on an existing database and you need a safe recovery path.
When not to use it
- Authoring Helm chart templates or
_helpers.tpl — use helm-chart-expert.
- Cloud firewall rules, tunnels, or general ingress debugging — use
kubernetes-deployment.
- Checking whether a database migration is safe to run — use
migration-check.
- Detecting drift between committed IaC and the live cluster — use
infrastructure-as-code.
How to invoke
Skill(skill: "infra-pack:keycloak")
Invoke it, then describe the operation and the target environment. The skill picks the matching pattern and walks the four-step procedure: identify the operation, check that the Keycloak pod is up, apply the config, then verify against the OIDC discovery endpoint.
Inputs
operation — one of realm-setup, auth-integration, debug-auth, bootstrap-recovery, helm-values — required.
environment — the target environment, such as localdev, <envAlias>, or prod — required.
symptom — the error message or observed behavior you are debugging — optional.
What you get back
Configuration YAML, Auth.js TypeScript, or an ordered sequence of kubectl/curl commands — capped at about 150 lines, one section per operation. Debugging answers come as ranked diagnosis steps; recovery answers come as a numbered procedure with safety warnings. Passwords always appear as placeholders such as $KC_ADMIN_PASSWORD, never as literals.
Worked example
Skill(skill: "infra-pack:keycloak")
"Login on <envAlias> returns error=Configuration after the callback.
operation: debug-auth, environment: <envAlias>"
The skill has you decode the JWT and compare its iss claim against KEYCLOAK_ISSUER. The pod exchanges tokens over internal cluster DNS, so Keycloak stamps the internal URL into iss and Auth.js rejects it. You get back Helm extraEnv adding KC_HOSTNAME_URL with the public HTTPS base URL alongside KC_HOSTNAME, plus a curl <issuer>/.well-known/openid-configuration check to confirm the fix.
Related
helm-chart-expert — prefer it when the change is in the chart templates, not the Keycloak values.
kubernetes-deployment — prefer it when Keycloak is unreachable at the network layer rather than misconfigured.
infrastructure-as-code — prefer it when Helm values were changed by hand and you need to detect the drift.