Skip to content
Published on

Building SSO with Keycloak — From Realm, Client, and Flows to the 2026 New Features

Share
Authors

Introduction — Why You Should Not Build Login Yourself

Say you have 20 internal apps. If you implement sign-up, password reset, 2FA, social login, and session management separately in each one, your attack surface grows twentyfold. The idea behind SSO (Single Sign-On) is simple — stand up one server dedicated to the job of logging people in, and have every app delegate the question "who is this person?" to that server. The user logs in once and can use all 20 apps, and the apps never touch a password at all.

Keycloak is the open-source standard for this identity server (currently a CNCF incubating project, and the upstream of Red Hat build of Keycloak). It speaks both OIDC and SAML, and ships social login brokering, LDAP integration, 2FA, and WebAuthn out of the box. This post makes sense of Keycloak through four core concepts, walks the login flow one step at a time, and then covers deployment pitfalls and the 2026 new features. If the authentication flow itself feels unfamiliar, playing with the Auth Flow Visualizer first will make this post much easier to follow.

Part 1 — Keycloak Through Four Core Concepts

The Keycloak admin console has dozens of menus, but the skeleton is just four.

Concept            Role                                       Analogy
─────────────────  ─────────────────────────────────────────  ──────────────────
Realm              A fully isolated tenant (own users/config)  A single "kingdom"
Client             An application that delegates auth           Who the kingdom issues "passes" to
Role / Group       Permissions and user groupings              "Titles" and "departments"
Identity Provider  An external login source (Google, upstream OIDC/SAML)  "Recognizing another kingdom's passport"
  • Realm is the isolation boundary. A single Realm holds its own users, clients, roles, and login settings, and is completely separated from other Realms. The master Realm, where the admin account lives, should be used only to manage Keycloak itself; the standard practice is to spin up a separate Realm for your actual service. If your instinct for "I want to separate users per customer" is a Realm — Organizations, which we'll see later, is often the better answer.
  • Client is the "app that delegates authentication." A SPA running in the browser cannot hide a secret, so it is a public client (PKCE required), while a backend server can protect a secret, so it is a confidential client. Each client carries its own settings for protocol (OIDC or SAML), allowed redirect URIs, token lifetimes, and so on.
  • Role / Group is the permission model. A Role is a "title" (admin, editor), and a Group is a "department" (a bundle of users plus common roles and attributes). You can also build a hierarchy by nesting one role inside another as a composite role. The principle: think of roles as the permissions your app understands and groups as the way you organize people.
  • Identity Provider (IdP) is an "external login source." It brokers Google or GitHub social login, or your company's upstream OIDC/SAML IdP, with Keycloak sitting in front as the intermediary. LDAP and Kerberos attach not as an IdP but through User Federation (reading the user store directly).

Part 2 — The OIDC Login Flow, One Step at a Time

The most common flow is Authorization Code + PKCE. Let's follow the process of a SPA delegating login to Keycloak.

① The user clicks the app's "Log in"
   → The app redirects the browser to the Keycloak authorize endpoint
     (including client_id, redirect_uri, scope=openid, code_challenge)

② Keycloak shows the login screen → the user authenticates (password + OTP, etc.)
   ※ If a Keycloak session already exists, this screen is skipped = the heart of SSO

③ Keycloak redirects back to redirect_uri + a one-time authorization code

④ The app backend (or SPA) exchanges code + code_verifier at the token endpoint
   → receives access_token (JWT) + id_token + refresh_token

⑤ The app sends the access_token in the API call's Authorization header
   The API only verifies the signature with Keycloak's public key (JWKS) — it does not ask Keycloak on every request

Three things are key here. First, PKCE (code_challenge/code_verifier) neutralizes a stolen authorization code — it is mandatory for public clients. Second, the "skipped if a session exists" in ② is precisely SSO. When a second app delegates login, Keycloak already recognizes the user from the browser cookie and issues a code immediately, with no login screen. Third, in ⑤ the API does not call Keycloak on every request — because the access_token is a signed JWT, it only needs local verification with the public key. This statelessness is why SSO scales.

Don't get the roles of the three token types mixed up either:

id_token       "who logged in" — user info. Consumed by the app (client)
access_token   "what they can do" — permissions. Verified by the API (resource server)
refresh_token  "the right to be reissued" — used to renew when the access_token expires; store it securely

Tuning session and token lifetimes is the key lever in Realm settings. The standard is to make the access_token short (a few minutes) and the refresh_token long (up to the SSO session lifetime); the combination of a short access_token plus local signature verification gives you both "expires soon even if stolen" and "fast verification" at once.

Part 3 — Deployment: The Quarkus Two-Phase Model and Production Pitfalls

Since Keycloak 17, the distribution has been Quarkus-based (the old WildFly/JBoss distribution was removed). The pitfall newcomers hit most often here is the build/start two-phase model.

# ① Build phase (augmentation): "bake in" the DB vendor and enabled features
#    — this phase is slow but not done often
bin/kc.sh build --db=postgres --features=organizations,token-exchange

# ② Start phase: pass only runtime settings (hostname, DB connection, TLS)
bin/kc.sh start --optimized \
  --hostname=https://auth.example.com \
  --db-url=jdbc:postgresql://db/keycloak \
  --db-username=keycloak --db-password="$KC_DB_PASSWORD"

# For local development, a convenience command that combines both phases
bin/kc.sh start-dev

The --optimized flag means "I already built, so don't rebuild on start." In a container image, the standard is to put the build phase in the Dockerfile and bake it into the image, then run only start --optimized at runtime.

Three representative landmines you'll step on in production:

  • hostname v2 — from Keycloak 26, the hostname configuration changed completely. You now give --hostname a full URL (https://auth.example.com) rather than a hostname fragment, and to expose the admin console at a different address you give --hostname-admin separately. Behind a reverse proxy, add --proxy-headers=xforwarded too. Misconfigure it and your redirect URIs leak internal addresses or you fall into a login loop.
  • Production mode enforces TLSstart (not dev) will not come up without proper HTTPS, hostname, and DB. If a proxy terminates TLS, you must tell Keycloak with --proxy-headers so it correctly recognizes its own external address.
  • The DB is the entire state — the Keycloak server itself is nearly stateless; users, sessions, and configuration all live in an external DB (Postgres recommended). That makes horizontal scaling easy, but your DB backup and migration are your identity backup.

On Kubernetes, the Keycloak Operator wraps all of this declaratively via the Keycloak and KeycloakRealmImport CRDs. If you need a feel for Kubernetes, it helps to first get comfortable with CRD and operator patterns in the Kubernetes Playground.

Part 4 — Keycloak in 2026: The 26.x New Features

As of this writing, the latest is 26.6.3 (internal Quarkus 3.33.2). Of the things that graduated in the 26 series, four with large practical impact:

  • Organizations — released as a stable feature in 26.0, this is "the right answer for multi-tenancy." Until now, "per-customer isolation" was faked either by spinning up multiple Realms or with groups, and both hurt (Realms are heavy, and groups isolate weakly). Organizations puts per-org members, domains, and dedicated IdPs within a single Realm, and when you log in with an organization context, that membership is automatically carried in the OIDC token and SAML assertion. Over the course of 26.x it gained per-organization isolated group hierarchies and automatic group assignment based on external claims (IdP mappers). Remember it as "a Realm is a system that is a complete stranger to the others; Organizations are multiple customers within the same system."
  • JWT Authorization Grant (RFC 7523) — the standard way to obtain a Keycloak access token with an externally signed JWT assertion. As the recommended path replacing the old "external → internal token exchange," it lets you establish trust between services over a standard protocol.
  • Workflows — automating Realm administration tasks (e.g., cleaning up inactive users, follow-up actions triggered by specific events) has been formalized. Work that used to be done with custom extensions or an external cron has come into the server as a feature.
  • Zero-downtime patch releases — rolling updates between patch versions are supported, so you can apply security patches without stopping the cluster. This is a big step given that an identity server is infrastructure that "must never go down."

Token exchange still has boundaries — currently only OIDC/OAuth exchange is supported, and SAML-based client and IdP exchange is future work. So for establishing external trust, the JWT Authorization Grant above is the more standard answer.

Part 5 — When to Use It, and When to Think Again

Where Keycloak shines: when you need data sovereignty on-premises or in hybrid setups, when you need broad protocol (OIDC + SAML) coverage, and when you need to scale massively with no license cost. The fork in the road with managed SaaS (Auth0, Okta, Entra ID) is usually "will you bear the operational burden, or the cost and lock-in?" — Keycloak gives you complete control over your data and customization, at the price of owning the DB, upgrades, and availability yourself.

Points to reconsider: an identity server is a single point of failure. If it dies, all 20 apps become unable to log in at once. So multiple instances + external session storage (Infinispan/DB) + zero-downtime patching are a requirement, not a choice. And if you miss the security basics — token and session lifetimes, redirect URI whitelisting, PKCE enforcement — SSO instead concentrates your attack surface in one place. This sense is best built by running OAuth, OIDC, and JWT by hand in the Auth & Security Lab.

Closing

Keycloak is, in the end, infrastructure that "gathers login into one place." Build the skeleton with the four concepts — Realm (isolation), Client (app), Role/Group (permissions), and IdP (external integration); understand the flow with Authorization Code + PKCE; avoid deployment pitfalls with the build/start two-phase model + hostname v2; and solve multi-tenancy with Organizations — and the logins of 20 apps converge into one well-guarded door. Being open source while honestly following the standards is why this project has been loved for over a decade.

References