Skip to content

필사 모드: Keycloak 26 Architecture Deep Dive — Understanding Realms, Clients, and the Quarkus Runtime

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

As of 2026, Keycloak holds a de facto standard position in the open-source IAM (Identity and Access Management) space. Since becoming a CNCF incubating project, its release cadence has stabilized, and the latest version as of May 2026 is 26.6.2. Amid 2026 trends such as passkeys becoming the default, AI agent identity (non-human identity), and Zero Trust architecture, Keycloak is evolving rapidly with final support for the [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile.html), the JWT Authorization Grant, and experimental OAuth Client ID Metadata Document (CIMD) support.

However, operating Keycloak reliably in production — beyond just "spinning it up and using it" — requires an understanding of its internal architecture. This article covers the following.

- The changes brought by the transition from WildFly to Quarkus

- The domain model composed of realms, clients, users, roles, and groups

- How confidential vs public clients and client scopes work

- The internal structure of the authentication flow (browser flow) engine

- An overview of the database schema

- The two-phase build/start structure of kc.sh and its optimization

- New features in Keycloak 26.x and a production checklist

A History of Keycloak — From WildFly to Quarkus

Keycloak began as a Red Hat project in 2014 and was initially a traditional Java EE application running on top of the JBoss/WildFly application server. The WildFly-based distribution had the following limitations.

- Complex XML configuration centered on standalone.xml

- Slow startup times (tens of seconds) and a large memory footprint

- A poor fit for container/Kubernetes environments (conflicting with the immutable infrastructure paradigm)

- A heavy runtime carrying the entire set of Java EE subsystems

The Quarkus transition, which started as the Keycloak.X project in 2021, became the default distribution in Keycloak 17 (2022), and the WildFly distribution was removed entirely after version 19. The core of the Quarkus-based transition is build-time optimization.

| Aspect | WildFly distribution | Quarkus distribution |

| --- | --- | --- |

| Configuration | Editing standalone.xml | conf file, environment variables, CLI options |

| Startup time | 20+ seconds | Within a few seconds |

| Memory usage | High | Comparatively low |

| Extension deployment | EAR/WAR modules | JARs in the providers directory |

| Container friendliness | Low | Very high (optimized official images) |

| Applying config changes | Restart | Re-run the build phase, then start |

Quarkus pushes as much work as possible — CDI wiring, classpath scanning, configuration parsing — to build time. Consequently, Keycloak cleanly separates "build options" from "runtime options," and that separation is the reason the kc.sh build phase, covered later, exists at all.

The Domain Model — Realm, Client, User, Role, Group

Every concept in Keycloak can be understood as a tree rooted at the realm.

+--------------------------------------------------------------+

| Keycloak Server |

| |

| +--------------------+ +--------------------+ |

| | Realm: master | | Realm: production | |

| | (admin use only) | | | |

| +--------------------+ | +--------------+ | |

| | | Clients | | |

| | | - web-app | | |

| | | - api-gw | | |

| | | - cli-tool | | |

| | +--------------+ | |

| | +--------------+ | |

| | | Users | | |

| | | - alice |--+-- Groups |

| | | - bob | | Roles |

| | +--------------+ | |

| | +--------------+ | |

| | | Identity | | |

| | | Providers | | |

| | +--------------+ | |

| +--------------------+ |

+--------------------------------------------------------------+

Realm

A realm is an isolation unit that contains users, clients, roles, groups, authentication flows, and token policies. Think of it as a tenant boundary. From an operational standpoint, the important principles are:

- Use the master realm exclusively for managing Keycloak itself, and keep application users in separate realms.

- Once the number of realms exceeds several hundred, cache pressure and startup time become a burden — if you need multi-tenancy, evaluate the organizations feature (promoted to GA in 26) first.

- Token lifetimes, password policies, and brute-force protection are configured independently per realm.

Client

A client is an application or service that delegates authentication to Keycloak. It corresponds to a Relying Party in OIDC terminology and a Service Provider in SAML terminology. Web apps, SPAs, mobile apps, backend APIs, and CLI tools are all registered as clients.

User, Role, Group

- A user is an authentication subject belonging to a realm. It carries credentials (password, OTP, passkey), attributes, and federated identity links.

- A role is a unit of authorization, with two kinds: realm roles and client roles. Client roles belong to a specific client namespace.

- A group is a collection of users that can be hierarchical; mapping a role to a group means its members inherit the role.

- A composite role is a role that contains other roles. Overusing composites makes permission tracing difficult, so the standard practice is to prefer group-based mapping.

The criteria for choosing between roles and groups can be summarized as follows.

| Criterion | Role | Group |

| --- | --- | --- |

| Meaning | Expression of a permission | Expression of an organization/user set |

| Hierarchy | Can be emulated with composites | Natively supported |

| Token representation | realm_access, resource_access claims | groups claim (requires a protocol mapper) |

| Recommended pattern | The unit your application checks | The operational unit for bulk role assignment |

Confidential vs Public Clients

The OAuth 2.0 client type distinction is expressed in Keycloak configuration via the Client authentication toggle.

| Aspect | Confidential Client | Public Client |

| --- | --- | --- |

| Secret storage | Possible (server side) | Impossible (browser/mobile) |

| Examples | Spring Boot backend, API gateway | SPA, native mobile app |

| Client authentication | client secret, private_key_jwt, mTLS | None |

| PKCE | Recommended | Mandatory (per OAuth 2.1) |

| Token endpoint access | With the secret | With the code_verifier |

The [OAuth 2.1 draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/) mandates PKCE for all clients and removes the Implicit and ROPC grants. In 2026, enabling PKCE (S256) for every new client regardless of type is standard practice. In Keycloak, you can enforce S256 via the Proof Key for Code Exchange Code Challenge Method setting under the client Advanced tab.

Authentication methods for confidential clients are also evolving. Rather than a plain client secret, private_key_jwt (signature-based) or mTLS is recommended, and the Federated client authentication feature added in 26.6 lets clients authenticate with JWTs issued by workload identity systems such as SPIFFE/SVID — removing the secret entirely.

Client Scopes — Assembly Units for Token Contents

A client scope bundles "which claims and roles go into the token" into a reusable unit. To be precise, it is a bundle of protocol mappers (claim generators) and role scope mappings (role filters).

- Default scopes: always reflected in tokens. The defaults include profile, email, roles, and web-origins.

- Optional scopes: only reflected when the client explicitly requests them via the scope parameter. Examples include address, phone, and offline_access.

For example, when an SPA sends an authorization request like this:

GET /realms/production/protocol/openid-connect/auth?

client_id=web-app

&response_type=code

&scope=openid profile email offline_access

&redirect_uri=https://app.example.com/callback

&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM

&code_challenge_method=S256

openid, profile, and email are included automatically as default scopes, while offline_access is an optional scope that must be requested explicitly for the refresh token to be issued as an offline token. To prevent token bloat, minimize the default scopes and manage the aud claim explicitly with an audience mapper. The issued access token payload looks roughly like this.

{

"exp": 1765540800,

"iat": 1765540500,

"iss": "https://sso.example.com/realms/production",

"aud": ["api-gateway"],

"sub": "f3a2c1d0-1234-5678-9abc-def012345678",

"typ": "Bearer",

"azp": "web-app",

"scope": "openid profile email",

"realm_access": { "roles": ["user"] },

"resource_access": {

"api-gateway": { "roles": ["orders:read"] }

},

"preferred_username": "alice"

}

The Authentication Flow Engine — Inside the Browser Flow

Authentication in Keycloak operates as a tree-structured execution engine called an authentication flow. The browser flow is the most representative, executed when a user arrives at the authorization endpoint.

Browser Flow (default structure)

|

+-- Cookie [ALTERNATIVE]

| (pass immediately if an SSO session cookie exists)

|

+-- Identity Provider Redirector [ALTERNATIVE]

| (redirect to an external IdP if kc_idp_hint is present)

|

+-- Forms [ALTERNATIVE]

|

+-- Username Password Form [REQUIRED]

|

+-- Browser - Conditional 2FA [CONDITIONAL]

|

+-- Condition - User Configured [REQUIRED]

+-- OTP Form [ALTERNATIVE]

+-- WebAuthn Authenticator [ALTERNATIVE]

Each node is called an execution and consists of three elements.

1. Authenticator — an SPI implementation that performs the actual authentication logic (cookie check, password form, OTP validation)

2. Requirement — one of REQUIRED, ALTERNATIVE, CONDITIONAL, or DISABLED

3. Sub-flow — a group of executions (with its own requirement)

The evaluation rules are simple. At a given level, if any ALTERNATIVE succeeds, that level passes; every REQUIRED must succeed for the level to pass. A CONDITIONAL sub-flow behaves like REQUIRED only when its inner condition executions evaluate to true. With just these rules, the structure above produces the behavior "skip the form if an SSO cookie exists; otherwise ask for a password, and only users with OTP configured get 2FA."

In 26.x, passkeys are integrated into the login form (conditional/modal UI): via WebAuthn conditional UI, users can pick a passkey from the browser autofill without typing a username, supported by the default theme. For details, see the [WebAuthn Level 3 specification](https://www.w3.org/TR/webauthn-3/) and the [FIDO Alliance passkeys documentation](https://fidoalliance.org/passkeys/).

Once authentication completes, a user session is created, with per-client client sessions hanging beneath it. These sessions are stored in the Infinispan caches (persisted to the DB by default since 26), and an SSO cookie (KEYCLOAK_IDENTITY) is issued to the browser.

Database Schema Overview

Keycloak stores all configuration and users in a relational database via JPA (Hibernate). An overview of the core table groups:

REALM -- realm definitions and policies (many columns incl. token lifetimes)

REALM_ATTRIBUTE -- extended realm attributes (key-value)

CLIENT -- client definitions

CLIENT_ATTRIBUTES -- extended client attributes (PKCE enforcement, etc.)

CLIENT_SCOPE -- client scope definitions

PROTOCOL_MAPPER -- claim mapper configuration

USER_ENTITY -- user records (username, email, ...)

USER_ATTRIBUTE -- custom user attributes

CREDENTIAL -- hashed passwords, OTP, passkey metadata

USER_ROLE_MAPPING -- user-role mappings

KEYCLOAK_GROUP -- groups (hierarchy via the PARENT_GROUP column)

GROUP_ROLE_MAPPING -- group-role mappings

KEYCLOAK_ROLE -- realm/client role definitions

FEDERATED_IDENTITY -- external IdP account links

EVENT_ENTITY -- login events (when enabled)

ADMIN_EVENT_ENTITY -- admin event audit log

OFFLINE_USER_SESSION -- offline/persistent sessions

OFFLINE_CLIENT_SESSION -- persistent per-client sessions

MIGRATION_MODEL -- schema version tracking (Liquibase)

Useful operational points to keep in mind:

- Schema migrations are performed automatically at startup by Liquibase. Always back up the database before a major upgrade.

- EVENT_ENTITY grows without bound unless you configure a retention period. Configuring event expiration or shipping events to an external log system is mandatory.

- If attribute lookups are frequent, consider adding indexes on USER_ATTRIBUTE (26 strengthened indexes on key query paths).

- Modifying data with direct SQL is forbidden — it causes cache inconsistency. Always use the Admin API.

kc.sh — The Two-Phase Build and Start Structure

The most distinctive trait of Quarkus-based Keycloak is that configuration splits into build options and runtime options.

- Build options: database vendor, cache stack, health/metrics enablement, feature flags, etc. — changing them requires a rebuild

- Runtime options: DB URL/credentials, hostname, log level, HTTPS certificate paths, etc. — specified at start

Phase 1: build (run once when producing an immutable image)

bin/kc.sh build \

--db=postgres \

--health-enabled=true \

--metrics-enabled=true \

--features=token-exchange

Phase 2: start (runtime options only; --optimized skips the rebuild)

bin/kc.sh start --optimized \

--db-url=jdbc:postgresql://db.internal:5432/keycloak \

--db-username=keycloak \

--db-password=changeme \

--hostname=sso.example.com \

--proxy-headers=xforwarded \

--http-enabled=true

When building container images, the standard practice is to perform the build phase ahead of time in the Dockerfile to reduce startup time.

FROM quay.io/keycloak/keycloak:26.6 AS builder

ENV KC_DB=postgres

ENV KC_HEALTH_ENABLED=true

ENV KC_METRICS_ENABLED=true

RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:26.6

COPY --from=builder /opt/keycloak/ /opt/keycloak/

ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]

The start-dev mode is for development only. Its cache is local, HTTP is open, and hostname validation is relaxed — never use it in production.

Configuration Methods — Environment Variables, conf Files, CLI

The same option can be specified in three ways, with CLI arguments taking the highest precedence, then environment variables, then the conf file.

Method 1: CLI arguments

bin/kc.sh start --db-url-host=db.internal --log-level=info

Method 2: environment variables (option name uppercased with underscores, KC_ prefix)

export KC_DB_URL_HOST=db.internal

export KC_LOG_LEVEL=info

Method 3: the conf/keycloak.conf file

db-url-host=db.internal

log-level=info

In Kubernetes environments, the environment variable approach combines naturally with ConfigMaps/Secrets and is therefore the most widely used. Mounting secret values as files and referencing them via file references in option values is also supported. The full option list is available via per-subcommand help in kc.sh and in the [official server configuration guide](https://www.keycloak.org/server/configuration).

New Features in Keycloak 26.x

The 26.x series — especially the [26.6.0 release](https://www.keycloak.org/2026/04/keycloak-2660-released) — ships several headline features.

Workflows (realm management automation)

A feature for declaratively automating lifecycle tasks inside the server, such as disabling long-inactive accounts or revoking privileges after a set period. Work that previously required external batch jobs calling the Admin API can now be internalized as realm configuration.

Zero-downtime rolling patch

Starting with 26.6, cache compatibility is guaranteed between patch versions (such as 26.6.0 to 26.6.1), so Kubernetes rolling updates can replace pods sequentially without restarting the whole cluster. With the Operator, setting the update strategy to Auto performs the rolling update automatically after a compatibility check.

Federated client authentication

Clients authenticate with JWTs issued by an external trust authority (for example, [SPIFFE](https://spiffe.io/docs/) workload identity) instead of a client secret. This direction fundamentally eliminates secret distribution and rotation problems.

JWT Authorization Grant

Based on RFC 7523, the grant that exchanges an externally issued JWT assertion for an access token is now officially supported. Token brokering becomes much simpler for machine-to-machine integration.

And more

- Passkeys integrated into the login form (conditional/modal UI)

- FAPI 2.0 Security Profile & Message Signing Final support

- EdDSA signature algorithm support

- Experimental OAuth Client ID Metadata Document (CIMD) support — opening up the scenario of using Keycloak as an MCP (Model Context Protocol) authorization server, where AI agents identify themselves via a metadata URL without pre-registration.

Production Checklist

Finally, a checklist to run through before going to production.

[ ] No application users in the master realm

[ ] Admin console access restricted to a separate hostname or network

(--hostname-admin option, firewall/Ingress separation)

[ ] HTTPS termination and --proxy-headers settings are consistent

[ ] Production-grade DB (PostgreSQL recommended) with sized connection pool

[ ] Token lifetimes: 5-15 min access tokens, SSO idle/max policies defined

[ ] Refresh token rotation or revoke-refresh-token enabled

[ ] PKCE S256 enforced for all new clients

[ ] Wildcard redirect URIs banned (register exact URIs only)

[ ] Brute force detection enabled

[ ] Event logging + retention + external SIEM integration configured

[ ] Health/metrics endpoints enabled and wired into monitoring

[ ] Backups: periodic DB backups + realm exports

[ ] Upgrade path: tracking breaking changes in release notes

[ ] Unused features (e.g., the temporary admin account) cleaned up via feature flags

For token policy in particular, we recommend anchoring on the [OAuth 2.0 Security BCP (RFC 9700)](https://datatracker.ietf.org/doc/html/rfc9700). The essentials are sender-constrained tokens (DPoP/mTLS), short access token lifetimes, and refresh token rotation.

Troubleshooting — Common Problems and Causes

1. Invalid redirect_uri error

The most common initial configuration error. It occurs when the redirect URI registered on the client does not exactly match the redirect_uri in the authorization request.

Cause checklist:

- Protocol mismatch (http vs https)

- Trailing slash differences

- Missing port (https://app.example.com vs https://app.example.com:443)

- Relying on a wildcard (*) that was removed in production

The fix is simple: register the callback URL exactly (ideally without wildcards) in the client Valid redirect URIs setting.

2. Infinite redirects or wrong issuer behind a reverse proxy

If the proxy terminates TLS and Keycloak does not know about it, the iss claim in issued tokens and the redirect URLs are generated with internal addresses.

If the proxy sets X-Forwarded-* headers

bin/kc.sh start --optimized \

--hostname=sso.example.com \

--proxy-headers=xforwarded \

--http-enabled=true

Verify: confirm the issuer in the OIDC discovery document is the external address

curl -s https://sso.example.com/realms/production/.well-known/openid-configuration

Check that the issuer and authorization_endpoint in the discovery document both use the external hostname.

3. A build option changed at runtime is not taking effect

Build options like the database vendor or features are ignored — or trigger an automatic rebuild — when specified at start time. If you use the --optimized flag they are ignored; otherwise startup slows down. Build option changes must be handled by rebuilding the image.

4. All sessions logged out after an upgrade

Before 26, in-memory sessions were the default, so a full restart evaporated sessions. Since 26, persistent user sessions are the default and sessions survive in the DB, but a full restart may still be required between minor versions due to cache protocol incompatibility. Always check the upgrade section of the release notes.

5. Memory usage keeps growing

The common causes are these three.

1. Unbounded growth of EVENT_ENTITY / ADMIN_EVENT_ENTITY

-> configure event expiration, offload to an external SIEM

2. Excessive offline sessions (offline_access granted to every client)

-> restrict the offline_access scope to clients that truly need it

3. Unset JVM heap letting caches grow to the container limit

-> check JAVA_OPTS_KC_HEAP or container-memory-based auto sizing

Anti-Pattern Collection

- Using the master realm for regular user logins — raises privilege escalation risk and management complexity at the same time.

- Using the ROPC (Resource Owner Password Credentials) grant — a pattern removed in OAuth 2.1. For CLIs, use the Device Authorization Grant.

- Setting access token lifetimes to several hours — the blast radius on theft grows accordingly. 5-15 minutes plus refresh rotation is the standard.

- Direct UPDATE statements on the DB — causes unpredictable behavior through cache/DB inconsistency. Use only the Admin REST API or the Admin CLI (kcadm.sh).

- Wiring every role into composite roles — makes permission audits impossible. Unraveling with group-based mappings is the better approach.

Conclusion

Keycloak 26 has become a completely different piece of software from the "heavy SSO server of the WildFly era." The Quarkus-based build/runtime separation, declarative configuration, Kubernetes-friendly operational model, and standards support spanning passkeys and AI agent identity make Keycloak a strong central component for the identity-first Zero Trust architecture of 2026.

If you have absorbed the domain model and runtime structure covered here, the next steps are high-availability clustering and SPI extension development. We will cover each in depth in separate articles.

References

- [Keycloak Documentation](https://www.keycloak.org/documentation)

- [Keycloak Release Notes](https://www.keycloak.org/docs/latest/release_notes/index.html)

- [Keycloak 26.6.0 Release Announcement](https://www.keycloak.org/2026/04/keycloak-2660-released)

- [Keycloak Server Configuration Guide](https://www.keycloak.org/server/configuration)

- [OAuth 2.0 (RFC 6749)](https://datatracker.ietf.org/doc/html/rfc6749)

- [PKCE (RFC 7636)](https://datatracker.ietf.org/doc/html/rfc7636)

- [OAuth 2.0 Security Best Current Practice (RFC 9700)](https://datatracker.ietf.org/doc/html/rfc9700)

- [OAuth 2.1 draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/)

- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)

- [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile.html)

- [WebAuthn Level 3](https://www.w3.org/TR/webauthn-3/)

- [FIDO Alliance — Passkeys](https://fidoalliance.org/passkeys/)

- [SPIFFE Documentation](https://spiffe.io/docs/)

현재 단락 (1/250)

As of 2026, Keycloak holds a de facto standard position in the open-source IAM (Identity and Access ...

작성 글자: 0원문 글자: 19,485작성 단락: 0/250