Keycloak
Open-source identity and access management server. Keycloak runs OpenID Connect, OAuth 2.0 and SAML 2.0 for any number of applications, so each app hands login, sessions, MFA and user federation to one place and only checks signed tokens. It is a Java (Quarkus) server backed by a relational database such as Postgres, organised into realms, clients, users and roles. Apache 2.0, free, a CNCF incubating project, with Red Hat's build as the paid supported option. It fits when several apps and tools need one sign-on and the team can keep a stateful server patched.
Keycloak is an open-source identity provider. Applications send users to it to sign in, and it sends back signed tokens over OpenID Connect (OIDC) or SAML. Login screens, password storage, multi-factor authentication, sessions and links to outside identity sources then live in one server, and each application only checks a token. The official docs at keycloak.org/documentation own the option reference; the upgrading guide lists every change between releases.
What it is
A single Java server, built on Quarkus, that implements:
- OpenID Connect and OAuth 2.0 — authorization code with PKCE, client credentials, device authorization, token exchange, token introspection, and dynamic client registration.
- SAML 2.0 — as identity provider for applications that only speak SAML.
- Identity brokering — sign-in through Google, GitHub, another OIDC or SAML provider, with first-login account linking.
- User federation — LDAP and Active Directory, with Kerberos.
- Admin surfaces — an admin console, an Admin REST API, the
kcadm.shCLI, and an account console for end users.
Licence is Apache 2.0. Keycloak started at Red Hat and joined the CNCF as an incubating project on 10 April 2023. Red Hat build of Keycloak is Red Hat's supported distribution of the same code.
The current line is 26.8: 26.8.0 shipped on 2026-10-01 and 26.8.1 on 2026-10-13. A minor release ships about every three months, with patch releases every two to three weeks between them. Each line gets patches until the next minor ships. 26.8 brought OID4VCI credential issuance to preview, SCIM user provisioning enabled by default, the stateless mode (session data in the database, no external Infinispan) promoted to supported, and client secret rotation enabled by default. The 26.8.1 patch alone lists more than a dozen CVE fixes, most in fine-grained admin permissions, identity brokering and X.509 authentication.
How it works
Every application is a client inside a realm. A realm is an isolated tenant with its own users, roles, keys, login theme and settings. The master realm is for administering Keycloak itself; applications belong in their own realm. When a user signs in, Keycloak creates a user session (persisted to the database by default since 26.0) and issues three tokens: an ID token that says who the user is, a short-lived access token for calling APIs, and a refresh token that renews the access token without another login. All of them are JWTs signed with the realm's keys. Applications check the signature against the realm's published JWKS, so they do not call Keycloak on every request.
Two sign-in flows cover most self-hosted use: a web application that redirects the browser, and a command-line tool such as kubectl that opens a browser and keeps the token locally.
Step 1 of 5. The user opens the app without a session. The app redirects the browser to the realm's authorization endpoint with its client ID, redirect URI, state and a PKCE challenge.
Two paths: a web app using the OIDC code flow, and kubectl using an OIDC token checked by the Kubernetes API server.
The database is the source of truth; the embedded Infinispan cache holds hot realm, user and session data in memory, and the nodes of a cluster find each other through the database (jdbc-ping). Health and metrics endpoints are on a separate management port, 9000.
When to use it
Reach for it when:
- Several applications, dashboards and internal tools each need sign-in, and one login with one MFA policy should cover them all.
- Standard protocols matter: OIDC for modern apps, SAML for older vendor software, and an OIDC issuer that the Kubernetes API server and kubelogin can trust.
- Users come from somewhere else (Google, GitHub, LDAP, Active Directory) and need brokering or federation with account linking.
- Identity data must stay on owned infrastructure, with no per-user fees.
Skip it when:
- One application needs login. A library such as Auth.js, or the framework's own auth, has fewer moving parts.
- The only goal is "GitHub login in front of Kubernetes". Dex or a proxy such as oauth2-proxy is smaller.
- No one can patch a Java server and its database on a monthly cadence. A hosted identity provider moves that work to a vendor.
At a glance
Core concepts
- Realm — an isolated tenant: users, clients, roles, keys, flows and themes.
masteris for admins only. - Client — an application registered in a realm. Confidential clients hold a secret; public clients (SPAs, CLIs) use PKCE and no secret.
- Client scope and protocol mapper — decide which claims land in a token, for example a
groupsclaim from group membership. - Roles and groups — realm roles, client roles and groups; groups carry role mappings to many users at once.
- Authentication flow — the ordered steps of a login (password, OTP, WebAuthn, conditional 2FA), editable per realm.
- Identity provider and user federation — outside sources of users, brokered (OIDC/SAML) or federated (LDAP).
Key facts (26.8)
| Item | Value |
|---|---|
| Licence | Apache 2.0 |
| Runtime | Java on Quarkus; official image quay.io/keycloak/keycloak |
| Database | PostgreSQL, MariaDB, MySQL, SQL Server, Oracle, Aurora PostgreSQL; PostgreSQL 13 minimum, 18 supported since 26.5 |
| Ports | 8080/8443 for HTTP(S); 9000 for health and metrics |
| Install on Kubernetes | Keycloak Operator (official) or plain manifests |
| Release rhythm | Minor about every three months; patches every two to three weeks |
| Sessions | Persisted to the database by default since 26.0 |
Comparison
| Aspect | Keycloak | Dex | Authentik | Zitadel |
|---|---|---|---|---|
| Role | Full identity provider | OIDC federation shim | Full identity provider | Full identity provider |
| Own user store | Yes | Minimal (static users) | Yes | Yes |
| SAML | Yes | Connector only | Yes | Yes |
| Language | Java | Go | Python | Go |
| Licence | Apache 2.0 | Apache 2.0 | Open core, paid enterprise tier | AGPL-3.0 |
How to integrate
Run it in a container
Build an optimised image so start-up skips the build step, then run with runtime settings only. The bootstrap admin is temporary: replace it with a permanent admin account after first login.
FROM quay.io/keycloak/keycloak:26.8.1 AS builder
ENV KC_DB=postgres KC_HEALTH_ENABLED=true KC_METRICS_ENABLED=true
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:26.8.1
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]
# Container env on Kubernetes (values from a Secret in practice)
env:
- { name: KC_DB_URL, value: "jdbc:postgresql://postgres:5432/keycloak" }
- { name: KC_DB_USERNAME, valueFrom: { secretKeyRef: { name: keycloak-db, key: username } } }
- { name: KC_DB_PASSWORD, valueFrom: { secretKeyRef: { name: keycloak-db, key: password } } }
- { name: KC_HOSTNAME, value: "https://sso.example.com" }
- { name: KC_HOSTNAME_ADMIN, value: "https://sso-admin.example.com" }
- { name: KC_HTTP_ENABLED, value: "true" } # TLS ends at the proxy
- { name: KC_PROXY_HEADERS, value: "xforwarded" }
- { name: KC_BOOTSTRAP_ADMIN_USERNAME, value: "bootstrap-admin" }
- { name: KC_BOOTSTRAP_ADMIN_PASSWORD, valueFrom: { secretKeyRef: { name: keycloak-admin, key: password } } }
readinessProbe:
httpGet: { path: /health/ready, port: 9000 }
At the reverse proxy, publish /realms/, /resources/ and /.well-known/. Keep /admin/ and /realms/master/ internal, and never proxy port 9000.
Register a client with kcadm
kcadm.sh config credentials --server https://sso.example.com --realm master --user admin
kcadm.sh create realms -s realm=apps -s enabled=true
kcadm.sh create clients -r apps -s clientId=grafana -s publicClient=false \
-s 'redirectUris=["https://grafana.example.com/login/generic_oauth"]'
ID=$(kcadm.sh get clients -r apps -q clientId=grafana --fields id --format csv --noquotes)
kcadm.sh get clients/$ID/client-secret -r apps # read the secret
kcadm.sh create clients/$ID/client-secret -r apps # regenerate it (rotation)
Point an app at it (Grafana)
[auth.generic_oauth]
enabled = true
name = Keycloak
client_id = grafana
client_secret = $__env{GF_OAUTH_CLIENT_SECRET}
scopes = openid email profile
auth_url = https://sso.example.com/realms/apps/protocol/openid-connect/auth
token_url = https://sso.example.com/realms/apps/protocol/openid-connect/token
api_url = https://sso.example.com/realms/apps/protocol/openid-connect/userinfo
use_pkce = true
kubectl through OIDC
On K3s, pass the issuer to the API server, then use kubelogin in the kubeconfig. Add a Group Membership mapper to the client so the groups claim exists.
k3s server \
--kube-apiserver-arg=oidc-issuer-url=https://sso.example.com/realms/apps \
--kube-apiserver-arg=oidc-client-id=kubernetes \
--kube-apiserver-arg=oidc-username-claim=preferred_username \
--kube-apiserver-arg=oidc-groups-claim=groups
users:
- name: oidc
user:
exec:
apiVersion: client.authentication.k8s.io/v1
command: kubectl
args: [oidc-login, get-token,
--oidc-issuer-url=https://sso.example.com/realms/apps,
--oidc-client-id=kubernetes]
In the GL stack
Keycloak is the single sign-on for the products (builddaily.io, paiddaily.io, sagedaily.io and the others) and for the operator tools: Rancher, Grafana, Argo CD and kubectl, all over OIDC.
- Where it runs. It runs on the prod cluster, in the shared stack namespace.
- Version. It runs 26.0.6, which is behind. The current release is 26.8.1 (2026-10-13), eight minor lines later. 26.0.6 shipped in November 2024, and the 26.0 line itself stopped at 26.0.8 in January 2025, so the deployed build has had no security fixes for about 21 months. Fixes since then include redirect-URI hostname wildcards no longer matching other domains (26.6.3), admin-API privilege checks (26.7.x), and the CVE batch in 26.8.1. The upgrade path is to read each "Migrating to 26.x" section of the upgrading guide, back up the database, and step through the minors; schema changes run on start and do not roll back.
- Client secrets. An Argo Workflows CronWorkflow rotates the OIDC client secrets every month, so a leaked secret has a short life.
- Admin exposure. On public hosts, the
masterrealm sits behind Cloudflare Access, so the admin login is not reachable from the open internet. This matches the reverse-proxy guide's advice to keep/realms/master/internal.
Gotchas
- The issuer must match exactly. The
issclaim comes fromKC_HOSTNAME. If apps reach Keycloak by an internal URL but tokens carry the public one (or the reverse), every token check fails. Set the hostname to the URL that clients use. - Redirect URI wildcards changed in 26.6.3.
https://example.com*no longer matcheshttps://example.com.other.domain.com; it is now read ashttps://example.com/*. The docs recommend exact redirect URIs and no wildcards. - Refresh token lifetime changed in 26.8.0. Refresh token expiry is now capped by the user session idle timeout, and offline refresh tokens always carry an
expclaim. Clients that assumed long refresh windows log out sooner. - Disabled clients leave the audience (26.8.0). A token no longer lists a disabled client in
aud, and the client secret is hidden from callers that hold onlyview-clients. - No
groupsclaim by default. Kubernetes RBAC, Argo CD and Grafana role mapping need a Group Membership mapper on the client or a client scope. - Upgrades are one-way. Keycloak migrates the database schema on start. Back up the database before each minor upgrade; a downgrade means a restore.
- The bootstrap admin is temporary. The account created from
KC_BOOTSTRAP_ADMIN_*is meant for first login only. Create a permanent admin, then delete it. - Do not expose everything. Proxying
/publishes the admin console and admin API. Publish only the paths in the reverse-proxy guide's table.
Risks
- Patch cadence. Each minor line gets patches only until the next minor ships, and most patch releases carry CVE fixes. A deployment that is not upgraded every few months runs with known holes.
- Single point of login. When Keycloak is down, no one signs in to anything that trusts it. Keep break-glass local accounts on critical tools (Rancher, the cluster admin kubeconfig).
- Credential concentration. The database holds password hashes, client secrets and signing keys. It needs the same backup encryption and access control as the cluster's most sensitive data.
- Stewardship. The project is CNCF-governed and Apache 2.0, with Red Hat as the main contributor. Lock-in is low: apps speak standard OIDC or SAML, so another identity provider can replace it client by client.
Alternatives
- Dex — pick it when Kubernetes and a few tools only need to trust an existing GitHub, Google or LDAP login, with no user store to run.
- Authentik — pick it for a friendlier admin UI and built-in proxy outpost for apps without OIDC support, and accept a Python stack with a paid enterprise tier.
- Zitadel — pick it for multi-tenant B2B identity with a Go single binary, if AGPL-3.0 is acceptable.
- A hosted provider (Auth0, Okta, Cognito) — pick it when no one should operate an identity server; costs grow per active user.
Related
- Rancher, Grafana and Argo CD — operator tools that sign in through Keycloak.
- Argo Workflows — runs the monthly client-secret rotation.
- Cloudflare Tunnel — Access in front of the admin realm on public hosts.
- OpenBao and External Secrets — secret storage and delivery in the same stack.
- Postgres and K3s — the database layer and the Kubernetes distribution under the clusters.
- Official: Keycloak documentation, Upgrading guide, Reverse proxy guide, keycloak/keycloak releases.
