What this is about
In a person’s token, roles can be in two places:
- global under
resource_access.<client>.roles: these roles apply everywhere. - in the tenant in the organization claim under
organization.<alias>.resource_access.<client>.roles: these roles only apply when the request runs in this tenant.
On every request, CIAS builds the effective roles from this: the roles that apply to exactly this request. CDMS checks only against these.
Why replace instead of add? Nobody granted a globally granted role for this one tenant. If it applied too, a permission could “leak” from one context into a tenant it was never meant for. A tenant that has its own roles therefore decides alone which business roles apply in it.
The decision
| Is the tenant of the request dynamic? | Does the person have roles there, for any client? | effectiveUserRoles |
|---|---|---|
| no | – | the global client roles |
| yes | no | the global client roles |
| yes | yes | only the roles in the tenant, for its own client, plus the realm roles the organization grants |
The realm roles from realm_access.roles are always in effectiveUserRealmRoles, unchanged.
Before and after
flowchart LR
subgraph V["In the token"]
direction TB
G1["global:<br/>hr-employee-read<br/>report-read"]
O1["in tenant nordbau:<br/>hr-employee-edit"]
R1["Realm:<br/>allowed-tenant-context-switch"]
end
subgraph N["Effective in nordbau"]
direction TB
E1["Business roles:<br/>hr-employee-edit"]
E2["Realm roles:<br/>allowed-tenant-context-switch"]
end
O1 --> E1
R1 --> E2
G1 -. "drop away" .-> N
The four variants
When: Static tenant, SINGLE, or the person belongs to no organization.
The business roles are the global client roles of its own client. There is no organization claim, or it does not count.
Result: effectiveUserRoles = resource_access.<client>.roles
When: Dynamic tenant, but the person has no roles in the organization.
The organization only determines the tenant, not the roles. The global client roles apply. That is why simply switching on organizations in Keycloak takes nobody's permissions away.
Result: effectiveUserRoles = global client roles
When: Dynamic tenant, and the person has roles in the organization.
The roles in the tenant replace the global ones. From the organization claim, only the own client counts: the claim contains the tenant roles of all clients, and the others are none of this deployment's business.
Result: effectiveUserRoles = roles in the tenant for its own client
When: always
Realm roles like platform-admin, allowed-tenant-context-switch or allowed-user-context-switch always apply, in every tenant. An organization cannot add to them. Otherwise, whoever manages a tenant could give themselves platform permissions.
Result: effectiveUserRealmRoles = realm_access.roles
And the attributes?
The same pattern applies to attributes. If an attribute is registered per tenant, CIAS fetches the value for the active tenant and replaces the value from the token with it. If CIAS cannot fetch the value, it refuses the request instead of falling back to the value in the token. See One value per person or per tenant.
And after a user switch?
After a user switch with the header user-roles: target, CIAS builds the effective roles for the target person, following the same rules as above. If they are a member of the tenant’s organization and the membership carries roles, these replace their global client roles. Their realm roles apply as always. Without user-roles: target, the effective roles stay those of the logged-in person.
Next
- The two permission matrices
- Realm role, client role, organization role
- Model roles: how CDMS checks the effective roles