Project not found.

SOP: Keycloak Client & Realm Management

sop-keycloak-client-creation Sop

sop active keycloak

SOP: Keycloak Client & Realm Management

Purpose

This SOP describes how to create and manage Keycloak realms, clients, roles, and users on the pal-e platform. It covers both the Terraform path (primary — realm/client/role IaC via pal-e-services) and the admin console path (secondary — user management and emergency operations). The outcome is a working OIDC client with documented config, realm roles mapped to the ID token, and secrets wired into the consuming app's k8s deployment.

Background

The Keycloak server is deployed by pal-e-platform/terraform/modules/keycloak/main.tf (namespace, secret, PVC, deployment, service, theme configmap). Image: quay.io/keycloak/keycloak:26.0.7, H2 dev-file DB, Tailscale funnel TLS termination.
Realm and client configuration is managed via Terraform IaC in pal-e-services/terraform/keycloak.tf using the mrparkers/keycloak provider (v5.0). Configuration is declared in k3s.tfvars. Four realms and five clients are currently managed this way.
History: The original SOP (April 2026) was admin-console-only because no Terraform provider existed in the tree at that time. The IaC adoption has since landed in pal-e-services. The admin console remains the path for user management (creating users, assigning roles, resetting passwords) since users are not Terraform-managed.

Current State

Realm Purpose Roles
<code>platform</code> Pal-E Platform (Forgejo, Grafana, Harbor, admin dashboard) (none)
<code>westside-basketball</code> Westside Kings &amp; Queens Basketball admin, coach, player
<code>pal-e-docs</code> Pal-E Docs admin, user
<code>pal-enterprises</code> Pal Enterprises owner, client

Prerequisites

  • Access to ~/pal-e-services/terraform/ on archbox (for Terraform path).
  • Tailscale-connected device with browser access to https://keycloak.tail5b443a.ts.net/admin (for admin console path).
  • Keycloak admin credentials (stored in ~/secrets/pal-e-services/keycloak-admin.env; also in k8s keycloak-admin secret in keycloak namespace).
  • Decision made up-front: public client (PKCE, no secret, browser-only SPA) or confidential client (server-side, secret stays on server). Match the consuming app's architecture.
  • The exact production hostname for the consuming app (needed for redirect URIs and web origins).

Path A: Terraform (Primary — Realms, Clients, Roles)

Step 1: Add Realm (if new)

Edit ~/pal-e-services/terraform/k3s.tfvars. Add an entry to the keycloak_realms block:
Each project gets its own realm — do not reuse realms across projects.

Step 2: Add Client

Add an entry to the keycloak_clients block in the same file:

Step 3: Apply

Step 4: Verify

Step 5: Retrieve Client Secret (confidential clients)

After tofu apply, retrieve the client secret from the Keycloak admin console: Clients → {client} → Credentials tab → copy Client Secret. The Terraform provider uses ignore_changes = [client_secret], so the secret is generated by Keycloak and fetched manually.

Path B: Admin Console (User Management)

Users are not Terraform-managed. Create and manage users via the admin console:
  • Open https://keycloak.tail5b443a.ts.net/admin. Log in with admin credentials.
  • Switch realm dropdown to the target realm — never operate against master.
  • Users → Add user. Set username, email, first name, last name. Enable the user.
  • Credentials tab → Set password (set Temporary to OFF for test users).
  • Role mapping tab → Assign role → select realm roles.

Secrets Wiring

Four env vars needed in the consuming app's k8s secret:
Key Value Source
<code>KEYCLOAK_URL</code> <code>https://keycloak.tail5b443a.ts.net</code> Static, platform-wide
<code>KEYCLOAK_REALM</code> Realm name Matches realm key in tfvars
<code>KEYCLOAK_CLIENT_ID</code> Client ID Matches client_id in tfvars
<code>KEYCLOAK_CLIENT_SECRET</code> Generated by Keycloak Admin console Credentials tab
Update the k8s secret via kubectl create secret generic ... --dry-run=client -o yaml | kubectl apply -f -. Then add secretKeyRef entries to pal-e-deployments/overlays/{app}/prod/deployment-patch.yaml.
For apps using SOPS: store in overlays/{app}/prod/{app}-secrets.enc.yaml via sops. Never commit unencrypted secrets.

Network Policy

Keycloak has a default-deny ingress NetworkPolicy in pal-e-platform/terraform/network-policies.tf. New apps must be added to the allowlist before they can reach Keycloak. Add the app's namespace to the existing policy and run tofu apply.

OIDC Client Config Summary

Field Value Notes
Protocol OpenID Connect
Client ID <code>{app-slug}</code> Must match consuming app env var exactly
Client authentication <code>ON</code> (confidential) or <code>OFF</code> (public+PKCE) Drives whether a secret is generated
Standard flow Enabled Authorization Code flow
Implicit flow Disabled Deprecated, never used
Direct access grants Disabled ROPC is forbidden (deprecated in OAuth 2.1)
Service accounts Disabled Unless the app needs service-account flows
PKCE <code>S256</code> Mandatory for all clients
Redirect URIs Narrowest path possible Exact callback path for server-side; <code>/*</code> for SPA
Post-logout redirect URIs App's logout landing Required for RP-initiated logout
Web origins App's bare origin Drives CORS. Avoid <code>+</code>
Front channel logout ON Recommended for SLO
Access token lifespan Realm default (5 min) Override only with documented reason

State Parameter / CSRF (Consuming App)

This SOP creates the Keycloak side. The consuming app must implement the OIDC state parameter correctly — it is mandatory:
  • Generate a cryptographically random state value (≥128 bits) per authorization request.
  • Store it tied to the user's session before redirecting to Keycloak.
  • On callback, compare state from the query string to the stored value using constant-time comparison. Mismatch → reject with HTTP 400.
  • For PKCE clients, the same lifecycle applies to code_verifier.
Most OIDC libraries (OmniAuth, keycloak-js, PKCE helpers) handle this automatically.

Rules

  • Always confirm the realm dropdown before any operation — never operate against master.
  • One realm per project. Do not reuse realms across projects.
  • One client per app. Do not reuse clients across apps.
  • Always set PKCE Code Challenge Method to S256 — for both public and confidential clients.
  • Always disable Direct Access Grants (ROPC) and Implicit Flow.
  • Always use the narrowest redirect URI path consistent with the app architecture.
  • Always populate post-logout redirect URIs and web origins.
  • Confidential client secrets live ONLY in Keycloak DB and k8s secrets (or SOPS-encrypted overlay). No other copy.
  • The consuming app MUST implement OIDC state validation on the callback.
  • Realms, clients, and roles go through Terraform (pal-e-services). Users go through admin console.
  • Never modify existing clients as a side effect of adding a new one.
  • Never change realm-level settings (theme, password policy, token lifespan) as a side effect of client work.
  • convention-sveltekit-spa — pattern for public+PKCE clients (browser-only SPA flows).
  • review-1096-2026-04-25 — original scope investigation (admin-console era, now superseded by Terraform adoption).
  • pal-e-services/terraform/keycloak.tf — Terraform resources for realms and clients.
  • pal-e-services/terraform/k3s.tfvars — declarative realm/client/role config.
  • pal-e-platform/terraform/modules/keycloak/main.tf — Keycloak server deployment.
  • pal-e-platform/terraform/network-policies.tf — Keycloak ingress allowlist.