Keycloak Single Sign-On Across Distributed Sites, and the Parts That Bite
Realms, PKCE, token clocks, brokering and the database nobody budgeted for. What actually breaks when single sign-on spans a dozen sites, and what to decide before it does.
Keycloak single sign-on looks like a configuration exercise. One realm, one client, a redirect URI, a token in the response. It works on a laptop in twenty minutes. Then you have nine sites across four domains, two server rendered, one a single page app, one a twenty year old thing that only speaks SAML plus a corporate directory that owns the actual employee records. At that point you are not configuring an auth server. You are operating an identity system, and identity systems fail in ways the getting-started guide does not cover.
The failure mode I keep meeting is not a broken protocol flow. The flows are well specified and the libraries are competent. What breaks is topology, expiry semantics and the quiet fact that you have made one stateful Java service the gate on every first request in the company.
What follows is the set of decisions that are expensive to reverse. Checked against the current specifications and Keycloak's own documentation as of late August 2026.
Realms Share Nothing, Which Is the Modelling Constraint
Keycloak's administration guide is direct about it. Realms are "isolated from one another" and can only authenticate the users they control. That sentence has a consequence people discover after the topology is deployed: the SSO session lives inside a realm. Two realms means two logins. Model one realm per site because it felt tidy and you have built single sign-on that does not sign anyone on once.
The unit that maps to a site is the client, not the realm. One realm per user population and trust boundary. Employees in one, external customers in another, because those two groups need different password policies, different MFA requirements and genuinely should not share a user store. Then one client per application inside it.
Two things go wrong inside a shared realm. The first is role sprawl. Realm roles are a global namespace and they accumulate, because deleting one means knowing which of nine sites still checks for it. Client roles keep authorization local to the application that defines it, which is almost always what you want. The second is audience. An access token minted for one client does not automatically name another client in its aud claim. If your resource servers skip the aud check they will accept any token from the realm, and you have authentication wearing authorization's clothes. Client scopes with an audience mapper fix it. Nobody does this on day one.
The other trap is realm count. Keycloak's own scalability discussion reports realm creation time diverging exponentially as realms are added, with one reported test becoming unworkable somewhere past six hundred realms. A later maintainer note in that thread says 26.4 handles a thousand or more provided you keep growing the realm cache. Treat both as reported figures rather than numbers you can capacity-plan against. The structural point survives either way: a realm per customer is a scaling decision disguised as an organisational one. Since Keycloak 26 the Organizations feature is the supported alternative, multi-tenancy inside a single realm, each organisation bound to a domain and an identity provider so an email address routes the user without a realm selector in the URL.
The version of this I would not ship again is a realm per environment per team. It looks clean in the first sprint. Eighteen months later the realms have drifted, nobody can tell you which one has the corrected password policy, and the export files that were supposed to be the source of truth were last updated by hand.

You take N independent authentication systems, each with its own failure that affects one site, and you exchange them for one correlated failure that affects all N at once.
Authorization Code With PKCE Is Not a Recommendation Any More
In August 2026 the IETF published RFC 10017, OAuth 2.0 for Browser-Based Applications, as BCP 212. Best Current Practice, not a blog opinion. Browser-based public clients must implement PKCE, authorization servers must support it and the implicit grant is documented as deprecated because it exposes the access token in the URL. The OAuth 2.1 consolidation draft, currently draft-ietf-oauth-v2-1-15 from March 2026, goes further and removes the implicit grant and the resource owner password credentials grant outright while requiring PKCE for the authorization code grant.
The requirement in that draft which actually causes production incidents is not PKCE. It is exact redirect URI matching. Authorization servers must require clients to register the complete redirect URI including the path component, and must reject requests that do not match exactly. Keycloak will happily let you type a wildcard into Valid Redirect URIs. That field is the most common self-inflicted wound I see. A wildcard on a host that also has an open redirect or a user-controlled path turns your authorization endpoint into a code delivery service for somebody else. PKCE narrows the damage, since a stolen code cannot be exchanged without the verifier. It does not close it, because an attacker with script execution on the registered origin can reach the verifier too.
Which is the real limit of PKCE. It solves code interception and code injection. It does nothing about cross-site scripting. If your single page app holds an access token in memory and an attacker achieves script execution on your origin, the token is theirs and the flow that obtained it is irrelevant. RFC 10017 responds by ranking three architectures instead of prescribing one: a backend for frontend that holds tokens server side and talks to the browser over an HttpOnly cookie, a token-mediating backend that hands access tokens to the browser and the browser-based client that manages everything itself. Decreasing order of security. Increasing order of convenience.
Here is the tradeoff that does not make it into the README. Choose the backend for frontend and every site now maintains its own cookie session. You traded a token storage problem for a session synchronisation problem across N sites, and logout becomes materially harder. Single logout only works if every site implements OpenID Connect Back-Channel Logout and actually destroys local state when the logout token arrives. Plenty of implementations accept the POST, return 200 and do nothing useful. The user clicks log out, the Keycloak session ends and five site sessions carry on until they expire on their own.
Four Expiry Clocks, and Only One of Them Is the Access Token
An access token is a bearer credential. You want it short, because there is no revocation story for a token that a resource server validates locally. A user session is a UX property. You want it to survive a working day. These pull in opposite directions and Keycloak models them as separate objects with separate settings: access token lifespan, SSO session idle, SSO session max, refresh token lifespan.
The relationship people get wrong is that the refresh token is bounded by the session, not only by its own expiry. When the idle window lapses, refresh tokens attached to that session stop working. When the max ceiling is reached the session ends regardless of what the user was doing. So the effective session length is the idle window as renewed by activity, capped by the maximum. Read your realm's actual values in Realm Settings rather than trusting a defaults table you found online, this article included.
The failure I keep meeting is a browser tab that has been asleep. The laptop wakes, twenty tabs try to refresh at once, the idle window has already lapsed and every refresh fails. A well-written client sends the user back to the authorization endpoint. A common client catches the error, retries, catches it again and produces a tight loop against the token endpoint. Multiply by a floor of laptops opening at nine in the morning. The fix is not a longer token lifespan. It is a refresh path that distinguishes a permanent failure from a transient one and backs off on the second.
Refresh token rotation adds its own race. RFC 10017 requires servers issuing refresh tokens to browser apps to rotate them on each use or sender-constrain them, cap the total lifetime and never extend it beyond the original token's expiry. Rotation with two tabs means both tabs refresh, one wins and the loser presents a token that has already been used. Reuse detection then quite reasonably kills the whole session. You need exactly one refresh owner per browser context. Web Locks, a shared worker or a backend for frontend that makes the question disappear.
Check NTP on your resource servers while you are there. Token validation is arithmetic on timestamps. A host whose clock has drifted rejects valid tokens, and the error will not mention time.
Brokering to the Corporate IdP Moves the Trust, It Does Not Remove It
Identity brokering makes Keycloak a service provider to the upstream directory and an identity provider to your applications. It is usually the correct call. The corporate IdP already owns joiners and leavers, already enforces MFA and already has an audit trail somebody signed off on.
Two things bite. The first is account linking. The default first broker login flow matches an incoming external identity against an existing local account by email or username and offers to link them. If the upstream provider does not verify email addresses, that is an account takeover primitive: register upstream with somebody else's address and inherit their local account along with its roles. Decide deliberately whether you trust that claim, then configure the flow to say so out loud.
The second is that you now have two sessions. One at Keycloak and one at the upstream provider. Ending the Keycloak session does not end the upstream one, so the user clicks log out, clicks log in and is straight back in with no prompt. This gets filed as a security bug. It is not a bug. It is the configuration behaving exactly as written, and fixing it means propagating logout upstream, which the team that owns the corporate IdP may not permit.
Then staleness. A brokered user gets a local Keycloak account with mapped attributes and group memberships, and those are a snapshot taken at login. Someone is removed from a group upstream on Tuesday and keeps the corresponding role locally until their session ends and the mappers run again. If your authorization decisions carry real consequence, that window is your actual revocation latency and it is longer than anyone in the room assumes.
The question worth asking before any of this: if the corporate IdP already does authentication, lifecycle and MFA, what is Keycloak contributing? A protocol adapter for applications that cannot speak the upstream protocol, and a home for application-level authorization that has no business living in a corporate directory. If it is contributing neither, you added a hop and a dependency.
Keycloak Single Sign-On Is a Stateful Dependency With a Database and No LTS
Architecture diagrams draw the identity provider as a small box off to one side. It is not off to one side. It is a stateful Java service with a relational database, that database is now tier zero, and every login on every site is a write path through it.
Since Keycloak 26, persistent user sessions are the default. Sessions are stored in the database and cached in memory rather than living only in Infinispan. Keycloak's own announcement is candid about the trade: sessions survive restarts and upgrades, which is what everybody asked for, at the cost of higher database usage and slower session operations. Cache sizing becomes a real parameter rather than a default you ignore. The session caches hold ten thousand entries per node by default and evicted entries reload from the database on demand, so undersizing quietly converts a memory read into a database read on a hot path.
Then upgrades. Community Keycloak has no LTS release. Only the newest version receives active development and security fixes. Version 26.7.3 shipped on 31 August 2026 carrying twenty security fixes. That is the cadence you are signing up for. Your alternatives are a commercial build with a stated support lifecycle or running known vulnerabilities in the component that authenticates everyone. There is no third option where you deploy it and leave it alone for three years.
Database schema migration runs automatically at startup unless you set the migration strategy to manual and generate the SQL yourself. On a large user or session table that migration is a lock and a duration, and you did not measure either in staging because staging has two hundred users. The upgrading guide also notes that rolling updates in a clustered setup must proceed one node at a time. Plan the window rather than discovering it.
Now the decision that shapes your whole architecture. When Keycloak is unavailable, nothing new authenticates anywhere. That part is unavoidable. What is avoidable is whether existing traffic keeps working. A resource server that validates JWTs locally against a cached JWKS keeps serving until tokens expire. A resource server that calls the token introspection endpoint on every request stops the instant Keycloak does. That is one line of configuration in most libraries and it decides whether an identity outage is a login outage or a total outage. Cache the JWKS with a sensible TTL and handle key rotation properly, otherwise your first signing key rotation becomes the outage instead.
High Availability, and Whether You Actually Need Session Replication
Keycloak's high availability guidance now presents three shapes. A single cluster, optionally spread across availability zones, where the Kubernetes cluster itself is the single point of failure. Multi-cluster v1, two clusters with an external Infinispan deployment per site plus an external load balancer, explicitly unsupported beyond two availability zones. And multi-cluster v2, a preview in 26.7, which drops external Infinispan entirely, treats the synchronously replicated database as the single source of truth and propagates cache invalidation through a database outbox table.
The v2 cost is stated plainly in the documentation: roughly double the CPU usage and write IOPS on your database. You moved complexity out of Infinispan and into Postgres. For most teams that is the right direction, because you already know how to run, back up and monitor Postgres, and you do not know how to debug an Infinispan split brain at two in the morning. You have also concentrated the failure into the database, and synchronous cross-region replication has a latency floor set by the speed of light in fibre. That floor sits on the login path.
The session replication question deserves a blunt answer. Cross-site session replication is expensive and its characteristic failure is two sites disagreeing about whether a session exists. The alternative is accepting that a site failover logs everybody out. For an internal manufacturing platform that is a five minute annoyance inside an event that is already an incident. For a consumer product it is a measurable churn event. Decide which one you are before procuring infrastructure, because the cheap answer is genuinely correct for a large share of deployments and nobody wants to be the person who says so in the design review.
The piece you cannot skip is authentication session state. That is the short lived state between the redirect to the login page and the code exchange. Lose the node mid-flow without sticky sessions or shared state and the user gets an opaque error at the worst possible moment. Version 26.7 persists more of this volatile data to the database, which removes a class of bug that used to be miserable to reproduce.
The Case for Not Running Any of This Yourself
The strongest argument against everything above is that a managed identity provider deletes the entire category. Not reduces. Deletes.
Be specific about what self-hosting commits you to. Someone owns an upgrade cadence in a project with no LTS. Someone owns database migrations on a table that only grows. Someone owns JVM tuning, cache sizing and the HA topology. Someone owns the custom theme that breaks on major versions and the custom authenticator SPI that breaks harder. Someone reads the security advisories every month. Someone is on call for the service that gates every login, including the login to the monitoring tool you would need to diagnose it.
Teams underestimate this because the first six months are genuinely easy. One container, one Postgres, it works. The bill arrives in year two, at the first major version jump with customisation attached, and at the first outage outside working hours. I have watched more engineering time disappear into Keycloak operations than into any authentication capability it made possible. That is not an argument against Keycloak. It is an argument for counting the cost before choosing it rather than after.
Self-hosting earns its keep when specific things are load bearing. Data residency, which is not theoretical if you operate in China, under EU rules or inside a regulated manufacturing environment. Protocol coverage, because the system running your line was commissioned in 2006 and speaks SAML. Pricing that does not scale with monthly active users. Custom authentication logic no SaaS will host. If none of those apply, the honest recommendation is to pay somebody. Managed Keycloak hosting also exists, which keeps the protocol coverage and hands off the operations.
There is an irony in brokering worth sitting with. If you federate to a corporate IdP that is itself a managed service, you already accepted somebody else running your identity. You just added a hop in front of it.
The Coupling That Is Not in the Diagram
Single sign-on is a centralisation technology sold as a convenience feature. The convenience is real. So is the centralisation, and it never appears in the diagram, where the identity provider is invariably a small box at the edge with thin arrows pointing towards it.
Here is what the trade actually is. You take N independent authentication systems, each with its own failure that affects one site, and you exchange them for one correlated failure that affects all N at once. That trade is usually worth making, because nine separate password stores is worse along every axis including security. But it is a trade, and the moment a team stops describing it as a trade they stop designing for the failure. Nobody writes a runbook for a dependency they have stopped thinking of as a dependency.
The teams that run this well are not the ones with the most elegant realm topology. They are the ones who wrote down, before anything broke, what each site is supposed to do for the ninety seconds after Keycloak stops answering.
Sources
RFC 10017 (BCP 212), OAuth 2.0 for Browser-Based Applications, IETF, August 2026: https://www.rfc-editor.org/rfc/rfc10017.html
The OAuth 2.1 Authorization Framework, draft-ietf-oauth-v2-1-15, IETF, March 2026: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-15
Keycloak Server Administration Guide (realm isolation, sessions, timeouts): https://www.keycloak.org/docs/latest/server_admin/index.html
Keycloak 26.7.3 released, 31 August 2026: https://www.keycloak.org/2026/08/keycloak-2673-released
Storing sessions in Keycloak 26, Keycloak blog: https://www.keycloak.org/2024/12/storing-sessions-in-kc26
Keycloak, Configuring distributed caches: https://www.keycloak.org/server/caching
Keycloak, High availability introduction and multi-cluster options: https://www.keycloak.org/high-availability/introduction
Keycloak Upgrading Guide (database migration, rolling updates): https://www.keycloak.org/docs/latest/upgrading/index.html
Frequently Asked Questions
Do I need one Keycloak realm per site?
No, and doing it defeats the purpose. Keycloak's SSO session is scoped to a realm and realms are isolated from one another, so two realms means two separate logins. Model one realm per user population and trust boundary, for example employees in one and external customers in another, then create one client per application inside that realm.
Is authorization code with PKCE still the right OAuth flow for a single page app?
Yes. RFC 10017, published as BCP 212 in August 2026, requires browser-based public clients to implement PKCE and documents the implicit grant as deprecated because it exposes the access token in the URL. The OAuth 2.1 draft removes the implicit grant entirely and requires PKCE for the authorization code grant.
What happens to my applications when Keycloak is down?
No new authentication succeeds anywhere. Whether existing traffic survives depends on how your resource servers validate tokens. Services that verify JWTs locally against a cached JWKS keep serving until their access tokens expire. Services that call the token introspection endpoint on every request fail the moment Keycloak becomes unavailable.
Does Keycloak still lose all user sessions when it restarts?
Not by default since Keycloak 26, which made persistent user sessions the default. Sessions are written to the database and cached in memory, so they survive restarts and upgrades. The tradeoff stated in Keycloak's own announcement is higher database usage and slower session operations compared with the previous in-memory-only design.
Why does my refresh token stop working before its configured expiry?
Because refresh tokens are bounded by the SSO session, not only by their own lifetime. When the SSO session idle window lapses without activity, or the SSO session max ceiling is reached, every refresh token attached to that session stops working regardless of the refresh token lifespan you configured.
Should I self-host Keycloak or use a managed identity provider?
Self-host only when data residency, protocol coverage such as SAML for legacy systems, pricing that does not scale with monthly active users, or custom authentication logic are genuinely load bearing. Community Keycloak has no LTS release and only the newest version receives security fixes, so self-hosting commits you to a continuous upgrade cadence or a commercial support contract.
Read next
China's University Major Cuts Are AI Policy, and Nigeria Should Read the Fine Print
The latest analysis essay.
Working on something in this space?
If this analysis is close to a problem you're thinking about, say so. I read every message personally.
Start a conversation