Skip to content
Ashish.
All posts
Diagram illustrating the flow from OAuth2 scope request through Keycloak Client Scopes and Protocol Mappers to a generated JWT payload.

Keycloak Client Scopes and Protocol Mappers Explained

A detailed look at Keycloak client scopes and protocol mappers for token customization and claim management.

By Ashish KumarPart 10 of Keycloak Masterclass Series

In the architecture of Keycloak, Client Scopes and Protocol Mappers form the engine room of token generation. Many developers treat these as simple checkboxes in the admin console, but they function as a strict data transformation pipeline. When an authorization server receives a request, it does not simply "attach" permissions to a token. Instead, it resolves a set of scopes, aggregates the associated Client Scopes, and then iterates through a defined list of Protocol Mappers to construct the final JSON Web Token (JWT). Understanding this mechanism is the only way to debug why a claim appears in one application but not another, or why a role is missing from a token despite being assigned to the user.

The process begins with the OAuth2 authorization request. When a client application requests an access token, it sends a scope parameter. This parameter is the trigger for scope resolution. Keycloak does not look at the client's configuration in isolation; it looks at the intersection of the requested scopes and the Client Scopes assigned to that client. There are three distinct types of scopes involved here: Standard Scopes (always present if the client supports OpenID Connect), Default Client Scopes (explicitly assigned to the client), and Optional Client Scopes (assigned to the client but only included if the client requests them or if the user grants consent).

Consider a scenario where the "E-Commerce App" client has the profile and roles scopes assigned as Default Client Scopes. If the app requests openid profile email, Keycloak first resolves the openid scope (which is mandatory for OIDC). It then checks the profile scope. Since profile is a Default Client Scope, it is automatically included without requiring user consent. However, if the app requests a custom scope like order_history which was assigned as an Optional Client Scope, Keycloak will only include it if the user explicitly consents to it during the authentication flow or if the client has pre-registered it as a default behavior. This aggregation phase happens before any token is created. The result is a definitive list of all scopes that must be represented in the token.

Once the scope list is resolved, the token generation engine iterates through the Protocol Mappers associated with those scopes. A Protocol Mapper is a rule that defines how a piece of data moves from a source to a target claim. The source can be a user attribute from the database (like an LDAP uid or a Keycloak email), a group name, a role, or even a hardcoded value. The target is the claim name that will appear in the JWT (like sub, email, or department).

The mechanism of a Protocol Mapper involves three critical steps: extraction, transformation, and assignment. First, the mapper extracts the value. For example, a "User Attribute" mapper configured to map employee_number to employee_id looks up the employee_number attribute in the user's profile. Second, it applies a transformation if one is defined. This could be a simple string format, a regex replacement, or a script execution. Third, it assigns the value to the target claim in the token. If the source value is null, the behavior depends on the mapper configuration: some mappers will omit the claim entirely, while others might inject a default value.

A common point of confusion is the interaction between multiple mappers defining the same claim. Keycloak enforces a specific evaluation order to resolve conflicts. For static mappers (like Hardcoded or Session ID), the last one defined in the list typically wins. However, for dynamic mappers (like User Attribute or Group), the logic is more nuanced. If two mappers try to write to the same claim, the one with the higher priority (lower index in the list) usually takes precedence, but this can be overridden by specific mapper configurations. The safest approach is to ensure unique claim names for each mapper or to explicitly configure the "Multivalued" option if you intend to append values rather than overwrite them. This strict ordering ensures deterministic token output.

To visualize this, let's walk through a concrete scenario involving a user named "Alice" and a client named "HR Dashboard". Alice has the following attributes in her profile: department = "Engineering", role = "Senior Developer". The HR Dashboard client has a Client Scope called "Employee Details" assigned. Inside this scope, there are two Protocol Mappers:

  1. Mapper A: Maps the User Attribute department to the claim org_department.
  2. Mapper B: Maps the User Attribute role to the claim job_title.

When Alice requests a token with the Employee Details scope, the Keycloak server executes the following sequence:

  1. It verifies Alice's identity.
  2. It resolves the Employee Details scope as active.
  3. It initializes an empty JWT claims object.
  4. It executes Mapper A: It reads department ("Engineering") and adds org_department: "Engineering" to the claims.
  5. It executes Mapper B: It reads role ("Senior Developer") and adds job_title: "Senior Developer" to the claims.
  6. It serializes the claims into the JWT payload.

Note that the name claim in the resulting payload is generated by the default OIDC 'Full Name' mapper associated with the standard profile scope, distinct from the custom Mappers A and B defined above.

The resulting token payload looks like this:

{
  "sub": "alice-id-123",
  "name": "Alice Smith",
  "org_department": "Engineering",
  "job_title": "Senior Developer"
}

This mechanism extends to Role Mapping as well. Keycloak uses a "Role Mapper" to translate Keycloak roles into claims. If you configure a Mapper to map the admin role to the claim is_admin with a value of true, the server checks if Alice has the admin role in the realm or client. If she does, the claim is injected. If she does not, the claim is omitted (unless a default value is set). This allows for fine-grained control over what downstream services see without altering the underlying user database.

It is crucial to understand that Client Scopes are not just containers; they are the filter that determines which mappers run. If a Client Scope is not included in the final resolved scope list, none of its Protocol Mappers execute. This is why you might see a claim missing from a token even though the mapper exists in the configuration. The scope must be requested, and the client must be authorized to use it. Furthermore, Client-specific scopes are merged with Realm-level scopes. If duplicate claim names exist, the Client-specific mapper typically overrides the Realm-level one due to evaluation order.

Another critical aspect of this mechanism is the handling of "Optional" scopes. In many OIDC implementations, a client might request a scope that is not strictly necessary for the core login flow. If the client requests an Optional Client Scope, Keycloak must check if the user has consented to share that data. If the user denies consent, the scope is stripped from the resolved list, and consequently, all Protocol Mappers attached to that scope are skipped. This provides a privacy-preserving layer where the application cannot access data it hasn't explicitly been granted permission to retrieve.

The distinction between "Client Scopes" and "Client Roles" is often blurred in documentation, but the mechanism treats them differently. Client Scopes define claims (attributes like email, name, custom fields). Client Roles define permissions (access rights). While a Role Mapper can convert a role into a claim, the primary purpose of a Role Mapper is to expose the role hierarchy to the token. If you need to pass a list of roles to a downstream API, you must configure a "Role Mapper" that points to the specific client roles. If you need to pass user attributes, you use a "User Attribute" mapper. Mixing these up leads to tokens that are either too large (including every role as a claim) or too small (missing critical attributes).

Finally, consider the impact of token refresh. When a token is refreshed, the same Client Scope and Protocol Mapper logic is re-executed. This means if a user's department attribute changes in the database, the next token refresh will reflect that change immediately, provided the same scopes are requested. This dynamic nature is effective but requires careful management of cache invalidation strategies on the client side. If your application caches the token claims, a change in the source attribute might not be visible until the token naturally expires and is refreshed.

Conclusion

In summary, Keycloak Client Scopes and Protocol Mappers act as a deterministic data pipeline. The scope parameter drives the selection of the pipeline stages, and the Protocol Mappers define the transformation rules. By treating these not as static settings but as an active evaluation order, developers can predict exactly what data ends up in a JWT. This understanding is essential for building secure, compliant, and interoperable identity systems where the integrity of the token claims is paramount.

While Keycloak offers a "Script" mapper for complex logic, using it for simple attribute mapping should be avoided unless absolutely necessary. The built-in mappers (User Attribute, Group, Hardcoded) are optimized and easier to audit. Introducing a Groovy script for a simple role-to-claim mapping adds a layer of debugging complexity that often outweighs the flexibility gained, especially in high-throughput systems where token generation latency matters.

The mechanism described here adheres to the OAuth 2.0 and OpenID Connect specifications, ensuring that the generated tokens are standard-compliant and can be consumed by any compliant resource server. However, the specific behavior of how Keycloak handles conflicting mappers or optional scopes is an implementation detail that varies slightly between versions. Since Keycloak 18, the evaluation order has been refined to improve consistency.

Ultimately, understanding this mechanism allows you to decouple your user data model from your API contract. You can change the underlying database schema or the internal role hierarchy without breaking your frontend applications, as long as you adjust the Protocol Mappers to map the new sources to the existing claims. This abstraction is the true value of Keycloak's scope and mapper architecture.

Common Pitfalls

  1. Overwriting claims with static mappers: Using multiple "Hardcoded" or "User Attribute" mappers that target the same claim name can lead to unexpected data loss if the evaluation order is not understood. Always verify which mapper wins in a conflict scenario.
  2. Confusing Client Roles with Client Scopes: Attempting to pass user attributes via a Role Mapper or expecting Client Scopes to grant API permissions will result in tokens that lack necessary data or fail authorization checks. Keep claims and permissions distinct.
  3. Missing consent for optional scopes: Assuming that an Optional Client Scope is always active can cause runtime errors in downstream services that expect certain claims. Remember that user consent is a strict requirement for Optional scopes to be included in the token.

Practical Takeaways

  • Scope Precedence: Default scopes are always included; Optional scopes require explicit user consent.
  • Mapper Conflict Resolution: Client-specific mappers generally override Realm-level mappers when duplicate claim names exist due to the evaluation order.
  • Dynamic Updates: Token claims reflect the latest user data on refresh, so cache invalidation strategies must account for this immediacy.

FAQ

Q: What is the difference between Default and Optional Client Scopes? A: Default Client Scopes are automatically included in every token generated for a client without requiring user consent. Optional Client Scopes are only included if the client requests them and the user explicitly grants consent during the authentication flow.

Q: How does Keycloak handle duplicate claims from different mappers? A: Keycloak follows a specific evaluation order. If multiple mappers target the same claim, the one with the higher priority (lower index in the list) typically wins, though static mappers may behave differently based on their specific configuration. Client-specific mappers usually take precedence over Realm-level ones.

Q: Does a token refresh update claims if user data has changed? A: Yes. When a token is refreshed, the Client Scope and Protocol Mapper logic is re-executed against the current user data. This means changes to user attributes are reflected in the new token immediately, provided the relevant scopes are requested.

Related posts