The problem
Acme Shop has two kinds of users and three applications. Customers sign up on shop.example; staff already have accounts in the company's Active Directory. Without a shared identity provider, shop-web and back-office would each store passwords and build their own MFA, sessions and password resets, and orders-api would have to trust each app's idea of who is calling. Every new app repeats that work, and a staff account that should be closed stays open in any app someone forgot.
An identity provider takes the job out of the apps. They send people to one sign-in service, which authenticates them and returns signed tokens, and APIs check the signatures. Keycloak is an open-source identity and access management server under the Apache License 2.0 that speaks OpenID Connect, OAuth 2.0 and SAML 2.0, and Acme runs it itself. It joined the CNCF at the incubating level in April 2023, and Red Hat sells a supported distribution, the Red Hat build of Keycloak. Versions and defaults on this page are those of Keycloak 26.8.0 (1 October 2026).
How it works
Realms. A realm is a separate space with its own users, credentials, roles, groups, clients, sign-in flows, signing keys and settings, and its own issuer. For realm acme that is https://id.shop.example/realms/acme, and its OpenID Connect endpoints sit below it under /protocol/openid-connect/: auth, token, certs, userinfo, logout, token/introspect, revoke and auth/device. The discovery document at /.well-known/openid-configuration lists them all; this page shortens them to /auth, /token and /certs. The built-in master realm is for administering Keycloak itself; applications get realms of their own.
Clients. Each application is a client of the realm and uses OpenID Connect or SAML. A client with Client authentication off is public: it can't keep a secret, as in a single-page or mobile app, so it depends on exact redirect URIs and PKCE. shop-web is one, with its PKCE method set to S256. With Client authentication on, the client is confidential and proves itself at the token endpoint with a secret, a signed JWT or a client certificate; back-office uses a secret. Switches on each client decide which grants it may use: Standard flow (authorization code), Direct access grants (the password grant, which RFC 9700 says not to use), Service account roles (client credentials), Standard Token Exchange, OAuth 2.0 Device Authorization Grant and OIDC CIBA Grant. A resource server such as orders-api is a client too, usually with every flow switched off, because it only receives tokens.
Users, roles and the claims in a token. Users live in the realm's database or in a federated directory (see User federation below). Roles are realm roles, such as customer, or client roles that belong to one client, and groups carry role mappings that their members inherit. What goes into a token is configuration, not code: client scopes (profile, email, roles, offline_access and your own) bundle protocol mappers, and each mapper writes a claim. By default realm roles land in realm_access.roles and client roles in resource_access.<client>.roles, and a token carries only the roles the user has and the client's scope allows. An Audience mapper puts orders-api into aud, so the API can refuse tokens that were meant for someone else.
Authentication flows. Sign-in is a flow of steps you can rearrange. The built-in browser flow tries Cookie first, and an existing SSO session ends the flow there. Next come Kerberos (disabled by default) and the Identity Provider Redirector, which jumps straight to a default or hinted provider, and then the forms: the Username Password Form, followed by the Browser - Conditional 2FA sub-flow, which asks for a one-time code only if the user has set one up. The default OTP policy is TOTP with 6 digits every 30 seconds; WebAuthn security keys and recovery codes sit in the same sub-flow, disabled by default. Passkeys have been supported since 26.4. With Enable Passkeys on (Realm settings, Login tab), the username forms offer a passkey through the browser's autofill or a Sign in with Passkey button, and the 2FA step is skipped after a passkey. Flows can be copied and changed: conditions, sub-flows, step-up by authentication level, required actions such as CONFIGURE_TOTP and UPDATE_PASSWORD, and custom authenticators. Brute-force detection is off in a new realm.
Sessions and single sign-on. A successful sign-in creates a user session, the SSO session, with one client session per application, and sets the KEYCLOAK_IDENTITY cookie on Keycloak's host. The next application that sends the browser there is answered from that session without a form. SSO Session Idle (30 minutes by default) ends a session nobody has used, SSO Session Max (10 hours) ends it in any case, and Client Session Idle and Client Session Max can shorten both for one client. Since 26.0, user sessions are stored in the database by default and cached in memory, so they survive restarts and upgrades. Signing out at the realm's logout endpoint ends the SSO session, and Keycloak tells the other clients by front-channel logout (the browser calls each client's logout URL) or back-channel logout (Keycloak posts a logout token to the client's back end). Offline tokens (scope=offline_access) outlive the session for background work and expire after 30 days without use.
Tokens and keys. Access tokens are JWTs signed with the realm's active key, RS256 unless you change Default Signature Algorithm, and they live 5 minutes (Access Token Lifespan). A refresh token expires when its client session would go idle, 30 minutes after it was issued by default, and never later than SSO Session Max; each refresh returns a new one. Revoke Refresh Token is off by default, so an old refresh token keeps working until it expires; switch it on to make every refresh token single-use, as in Refresh Token Rotation. The public halves of the realm's keys are published as a JWKS at /certs. A realm signs with one active key pair and can keep passive keys that only verify, so a rotation adds a new key with a higher priority and leaves the old one passive until the tokens it signed have expired.
Grants and protocols (status in 26.8):
| What | Status | Notes |
|---|---|---|
| Authorization code with PKCE | supported | S256 can be required per client |
| Client credentials | supported | Service account roles on a confidential client |
| Device authorization grant (RFC 8628) | supported | switched on per client |
| Standard token exchange (RFC 8693) | supported since 26.2 | Keycloak token for Keycloak token only, confidential clients only |
| Legacy token exchange (V1) | preview, deprecated | external tokens and impersonation |
| Token exchange delegation | preview | the act claim; disabled by default |
| JWT authorization grant (RFC 7523) | supported since 26.6 | |
| DPoP, FAPI 2.0 | supported since 26.4 | sender-constrained tokens |
| SAML 2.0 | supported | Keycloak as the identity provider for SAML apps |
SAML clients share users, flows and SSO sessions with the OpenID Connect clients, so an older SAML app and a new OIDC app sign in once.
User federation. An LDAP or Active Directory provider makes the directory's users usable in a realm. Keycloak never imports passwords: the directory checks them. It can import users into its own database the first time they appear and keep them current with a periodic full or changed-users sync, or, with Import Users off, read them from the directory each time. Edit mode decides where changes go: READ_ONLY, WRITABLE (written back to LDAP) or UNSYNCED (kept in Keycloak). Mappers bring in attributes, groups and roles, and the MSAD User Account mapper carries Active Directory's account state across, so a userAccountControl of 514, a disabled account, disables the user in Keycloak too. Kerberos (SPNEGO) gives single sign-on to domain-joined desktops, and the User Storage SPI connects any other user database.
Identity brokering. Keycloak can also be the client of another identity provider: any OpenID Connect or SAML 2.0 provider, or one of the built-in social providers such as Google, GitHub, Facebook, Microsoft and LinkedIn. The provider shows up as a button on the login page, or is chosen directly with kc_idp_hint. After the first sign-in, the first login flow creates a local user or links an existing one, and mappers copy claims onto it. The applications never talk to Google: they still get Keycloak's tokens.
Administration and auditing. Everything in the admin console is also in the Admin REST API (/admin/realms/{realm}/…) and the kcadm.sh command-line tool, so realms can be scripted and exported or imported as JSON. A SCIM API, supported since 26.8, lets provisioning systems manage a realm's users and groups, and fine-grained admin permissions (version 2, supported since 26.2) hand parts of a realm to a help desk. User events (LOGIN, LOGIN_ERROR, CODE_TO_TOKEN, REFRESH_TOKEN …) and admin events aren't stored by default, apart from errors written to the log, until you switch on Save events with an expiration; event listeners forward them elsewhere.
Themes and extensions. The login pages, account console, admin console, emails and welcome page are themes: FreeMarker templates, message bundles, stylesheets and images, chosen per realm. The default login theme has been keycloak.v2 since 26.0, and a custom theme extends a built-in one. Extensions such as authenticators, user stores, event listeners and protocol mappers are Java providers (SPIs) placed in providers/ and built into the server with kc.sh build.
Running it. Keycloak has been a Quarkus application since version 17, and the old WildFly-based distribution was removed in 20. kc.sh start-dev runs a development server with the dev-file database and local caches only. kc.sh start is production mode, which expects a hostname, TLS (or a proxy that terminates it) and a real database: PostgreSQL, MySQL, MariaDB, Microsoft SQL Server or Oracle, plus Amazon Aurora PostgreSQL and Azure SQL. Nodes form a cluster on their own, finding each other through the database (jdbc-ping, the default since 26.1), and share embedded Infinispan caches: local caches for realms, users and keys on each node, and distributed caches for authentication sessions, user sessions and action tokens. Health and metrics endpoints are served on a separate management port, 9000. On Kubernetes, the Keycloak Operator runs the server from a Keycloak resource and imports realms from KeycloakRealmImport resources.
Where it fits
- Solutions: customer sign-in for web and mobile apps, single sign-on for staff apps, B2B portals where each partner company signs in with its own identity provider (Organizations, supported since 26.0), API and microservice platforms that validate JWTs, multi-tenant SaaS with a realm or organization per tenant, and SAML for older enterprise apps.
- Patterns in this catalog it implements: OpenID Connect sign-in, the authorization code flow with PKCE for public clients, SAML SSO as the identity provider, Federated Identity through brokering and LDAP, Client Credentials for service accounts, Token Exchange, the Device Authorization Grant, Refresh Token Rotation with Revoke Refresh Token on, and the server-side half of Sessions vs Tokens: an SSO session behind short-lived tokens. The APIs do JWT Validation with its keys, or an API Gateway does it for them. Browser apps that shouldn't hold tokens at all put a Backends for Frontends in front, registered as a confidential client.
- Usual neighbours: a reverse proxy or load balancer in front (TLS, forwarded headers), PostgreSQL behind, an SMTP server for verification and password-reset emails, LDAP or Active Directory, external identity providers, the apps and APIs that use its tokens, and your metrics and log pipeline.
- Managed offerings: AWS has no managed Keycloak; teams run it on Amazon EKS or Amazon ECS with Aurora PostgreSQL. The Keycloak project's own high-availability tests run on Red Hat OpenShift Service on AWS with Aurora PostgreSQL, and its multi-cluster setup puts AWS Global Accelerator in front. AWS's managed identity provider is Amazon Cognito. Red Hat supports the Red Hat build of Keycloak, whose documentation was at 26.6 in October 2026; Auth0 (Okta) and Microsoft Entra ID are hosted alternatives.
When to use it
Use Keycloak when you want an identity provider you run and control: on your own servers or in your own cloud account, with standard protocols, LDAP, Active Directory and Kerberos for staff, brokering to social and enterprise providers, sign-in flows and pages you can reshape, and no per-user licence. Choose a managed service when nobody on the team should be on call for the login page, or when your users already live in one vendor's identity platform.
| Keycloak | Amazon Cognito | Auth0 (Okta) | Microsoft Entra ID | |
|---|---|---|---|---|
| Who runs it | you, optionally with Red Hat support | AWS | Okta, as a service | Microsoft |
| Cost model | no licence fee (Apache 2.0); you pay for servers, database and operations | per monthly active user, by feature plan (Lite, Essentials, Plus) | subscription plans priced by monthly active users | per-user licences (Free, P1, P2) for staff; External ID billed by monthly active users |
| Protocols for your apps | OpenID Connect, OAuth 2.0, SAML 2.0 | OpenID Connect, OAuth 2.0 | OpenID Connect, OAuth 2.0, SAML, WS-Federation | OpenID Connect, OAuth 2.0, SAML 2.0 |
| Users from elsewhere | LDAP and AD, Kerberos, OIDC and SAML providers, social logins | SAML and OIDC providers; Google, Facebook, Apple, Amazon | AD and LDAP through a connector on your network; SAML, OIDC, social | on-premises AD through Microsoft Entra Connect; social and enterprise providers in External ID |
| Custom logic | Java providers (SPIs) and flows | Lambda triggers | Actions (Node.js) | custom authentication extensions (calls to your REST API) |
| Default access-token lifetime | 5 minutes | 1 hour (up to 1 day) | 24 hours for a custom API | random, 60 to 90 minutes |
| Choose it when | you need control, self-hosting or on-premises directories | your apps run on AWS and you want no servers | you want a hosted service with many ready-made integrations | your staff already sign in with Microsoft |
Trade-offs
- You run a tier-0 service. When Keycloak or its database is down, nobody can sign in and no token can be refreshed. Plan for it like a database: several nodes across availability zones, a highly available database, backups, monitoring and someone on call.
- Revocation lags by one access token. Disabling a user or ending a session stops refreshes at once, but an API that checks signatures locally keeps accepting the access token it already has until
exp. Short lifespans bound that window; introspection closes it at the cost of a call per request (RFC 7662). - Upgrades are regular work. A minor release has come about every three months (26.6 in April, 26.7 in July and 26.8 in October 2026), with patch releases in between. Custom themes and providers have to be tested against each one; since 26.6, patch releases within a minor version can roll through a cluster without downtime.
- Sessions cost database work. With sessions stored in the database, every sign-in and refresh writes to it, and the multi-cluster v2 setup roughly doubles the database's CPU and write load, according to the documentation. The project reports testing a single cluster with 1,000,000 users at 300 requests per second, and scaling to 30 million users with 1,000 logins and 20,000 token refreshes per second on six large pods and an Aurora cluster; size your own deployment with a load test.
- Multi-site has limits. Multi-cluster v2, supported since 26.8 and switched on with
--features=stateless, keeps all session state in a synchronously replicated database and needs a commit latency below 10 ms (5 ms suggested), so it fits availability zones or nearby data centres in one region, not distant regions. Multi-cluster v1, the older setup with an external Infinispan cluster, is deprecated. - Many switches, and some defaults to change. The power to reshape flows and claims is also room for mistakes: wildcard redirect URIs, Direct access grants left on, brute-force detection and event storage off.
Implementation notes
The scenario's realm as an import file (abridged; the three timeouts are the defaults, written out for clarity):
{
"realm": "acme",
"enabled": true,
"accessTokenLifespan": 300,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000,
"roles": { "realm": [ { "name": "customer" } ] },
"clients": [
{
"clientId": "shop-web",
"publicClient": true,
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"redirectUris": [ "https://shop.example/callback" ],
"webOrigins": [ "https://shop.example" ],
"attributes": { "pkce.code.challenge.method": "S256" },
"protocolMappers": [
{
"name": "orders-api audience",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"config": { "included.client.audience": "orders-api", "access.token.claim": "true" }
}
]
},
{ "clientId": "back-office", "publicClient": false, "standardFlowEnabled": true,
"redirectUris": [ "https://backoffice.shop.example/callback" ] },
{ "clientId": "orders-api", "publicClient": false, "standardFlowEnabled": false,
"directAccessGrantsEnabled": false }
]
}
- Keep configuration as code. Export realms to JSON and import them with
KeycloakRealmImport,kcadm.shor the Admin REST API, so a change goes through review instead of a click in the console. Keepmasterfor administrators only. - Production start. Run
kc.sh buildwith your providers and options, thenkc.sh start --optimized, with the hostname set (--hostname https://id.shop.example), the proxy headers your load balancer sends (--proxy-headers xforwarded), and TLS on Keycloak or at the proxy. On Kubernetes, the Operator'sKeycloakresource holds the same settings:
apiVersion: k8s.keycloak.org/v2beta1
kind: Keycloak
metadata:
name: acme-id
spec:
instances: 3
db:
vendor: postgres
host: acme-id-db
usernameSecret: { name: acme-id-db, key: username }
passwordSecret: { name: acme-id-db, key: password }
http:
tlsSecret: acme-id-tls
hostname:
hostname: id.shop.example
proxy:
headers: xforwarded
- Make the APIs strict. Check
iss,aud,expand the algorithm on every token, cache the JWKS but fetch it again when a token names an unknownkid, and read roles fromrealm_access.rolesorresource_access. An API that checks only the signature accepts a token issued for any client of the realm; an audience mapper plus anaudcheck prevents that. - Change the risky defaults. Turn on brute-force detection, Save events (with an expiration) and Revoke Refresh Token, a realm setting that makes each refresh token single-use, which matters most for public clients such as shop-web. Turn off Direct access grants and the implicit flow where nobody uses them, and register exact redirect URIs.
- Keep the token window small. Leave access tokens at a few minutes. If an API must notice a disabled user or a logout sooner than
exp, let that API call the introspection endpoint instead of checking signatures only. - Back up the database. Since 26.0 it holds the sessions as well as the realms, users and credentials, and a restore is how you recover from a bad change.
- Directory changes reach Keycloak on a schedule. With imported LDAP users, set a changed-users sync, so attributes and account state from Active Directory arrive in time; passwords are always checked live against the directory.