Extending Keycloak for Single-Realm Multi-Tenancy
Keycloak's realm model allows any user in a realm to authenticate any registered client. There is no native mechanism to restrict specific users to specific clients within a single realm.
Organisations face a dilemma, they want operational simplicity through a single realm with unified user management and shared identity providers, but they also need to host multiple applications serving different user populations while preventing unauthorised access.
The standard workarounds are problematic. Multiple realms create operational overhead with duplicated configurations, cross-realm federation complexity, and administrative burden. Application-layer enforcement violates defence-in-depth principles by scattering security logic across applications, issuing tokens before denying access, and creating audit gaps where denials are not logged in centralised identity and access management (IAM) systems.
This article presents a third approach: custom authentication flow executors that enforce client access restrictions during authentication, before tokens are issued. The complete implementation is available online at GitHub.
Now, the question is: Why is it that built-in features do not solve these kinds of problems? The answer is simple. Keycloak's authorisation mechanisms operate after authentication, not during it. Client roles and scopes control permissions within applications after tokens are issued. Authorisation services enforce fine-grained resource access, but users already have tokens by that point. Client policies configure behaviour but do not restrict which users can authenticate.
The gap is clear: these features do not prevent authentication itself. Users attempting to access unauthorised clients complete the authentication flow and receive tokens, with denial happening downstream. What is needed is protocol-level denial before token issuance.
Solution architecture is also simple. Keycloak implements authentication as a configurable pipeline of execution steps. Each step receives context about the authentication attempt and either advances the flow or halts it with an error. By injecting a custom authenticator into this pipeline, we can validate client access before token generation.
The solution uses user attributes rather than external policy stores for consistency and simplicity, with no external dependencies during authentication and built-in audit logging. It denies authentication instead of limiting token claims to fail early and prevent token leakage. Using a custom authenticator provides protocol-level enforcement across Open Authorisation (OAuth 2.0), Security Assertion Markup Language (SAML), and other protocols with centralised logic that cannot be bypassed.
How is the Keycloak model implemented? The authenticator retrieves the user from the authentication context, failing securely if none exists. It extracts the client ID from the session context rather than user-supplied parameters to prevent impersonation attacks. After retrieving the allowlist from user attributes, it performs a membership check: if the requested client appears in the user's allowed clients list, authentication succeeds; otherwise, it fails with an access denied error.
The authenticator implements requiresUser() to signal that it must execute after user identification and configuredFor() to indicate whether a user has the allowlist configured. It follows a fail-secure approach where missing allowlists result in denied access.
The authenticator factory uses a singleton pattern since authenticators are stateless, reducing object allocation under high load. It implements ConfigurableAuthenticatorFactory for proper display in Keycloak's admin console and provides requirement options: REQUIRED for strict enforcement, ALTERNATIVE for gradual rollout, and DISABLED for temporary disablement.
For deployment and configuration, keycloak discovers custom components via Java's ServiceLoader mechanism, requiring a service file containing the factory's fully qualified class name. Deployment involves building the project, copying the JAR to Keycloak's providers directory, rebuilding the provider registry, and restarting Keycloak. For containers, the JAR is copied during image creation.
Configuration begins by duplicating an existing authentication flow and adding the Client Access Control step after user authentication forms, but before token generation. Incorrect positioning causes failures: before user forms returns no user, as ALTERNATIVE creates bypasses, and after token generation is too late.
User configuration requires creating a multi-valued ‘website’ attribute with admin-only modification permissions, then assigning allowed client IDs to individual users through their attributes.
For operational considerations, user attribute lookups are in-memory operations with minimal latency impact. Organisations should monitor cache hit rates and authentication execution times. For users accessing most clients, consider inverting the model to store denied clients instead.
Security requires ensuring that only administrators can modify allowlist attributes. Monitor for spikes in ACCESS_DENIED errors, users with empty allowlists, and repeated unauthorised attempts. Maintain defence in depth by implementing application-layer checks alongside protocol-level controls.
For bulk provisioning, use the Admin API to set attributes programmatically or map them from Lightweight Directory Access Protocol (LDAP) and Active Directory during user federation. Keycloak's event system logs all authentication failures for compliance reporting and audit trails.
We must know how to extend the pattern of the Keycloak model. This demonstrates context-aware authentication, a broader pattern applicable beyond client access control. The same approach enables time-based restrictions by validating authentication attempts against allowed hours, geographic restrictions by geolocating source internet protocols (IPs) and checking against allowed countries, and device posture validation by checking device fingerprints against trusted devices. The pattern is consistent: store context-specific policies in user attributes, extract relevant context during authentication, and enforce before token issuance.
In summary, single-realm architectures simplify operations but need client-level access controls unavailable in standard Keycloak. Custom authentication executors are enforced at the protocol layer by denying authentication rather than just authorisation, centralise policy through user attributes, maintain audit trails through Keycloak's event system, scale efficiently with constant-time lookups, and require no client changes.
The key insight is that Keycloak's authentication flows are programmable pipelines. Custom executors encode business-specific security requirements at the right point during authentication, before credentials are issued. This pattern extends to any scenario requiring context-aware authentication decisions enforced at the IAM layer.
