What this is about
Normally a request runs in the tenant from the token. With two headers a client can deviate from that:
- Header
tenant: work in another tenant - Header
user: work on behalf of another person
Any client can set both headers, so they are not trustworthy. Whether they take effect is decided by information from the signed token.
For the header tenant, CIAS distinguishes two cases that are handled very differently:
- Selection: The target is an organization the person is a member of themselves. No special role needed.
- Privileged switch: The target is a tenant the person is not a member of. For this they need the realm role
allowed-tenant-context-switch, and the target must be in their list of allowed tenants.
A realm role is a role in Keycloak that applies to the whole platform, not just in one tenant. No organization can grant it.
The order in the filter chain
sequenceDiagram
participant C as Client
participant F as Filter chain
participant G as Tenant gate
C->>F: Token (tenant acme) + header tenant: globex
F->>F: Read headers, then token
F->>F: resolve: globex an own organization? no → acme
F->>G: Is acme served?
G-->>F: yes
F->>F: Determine roles and attributes for acme
F->>F: Switch: role allowed-tenant-context-switch? globex allowed? → tenant := globex
F->>G: Is globex served?
G-->>F: yes
F-->>C: Request runs in globex, with the roles from acme
The selection happens in the resolution, the privileged switch after it. That is why only the selection determines the roles: the roles are fixed between the two steps.
The decision table
| Target is an own organization | Realm role allowed-tenant-context-switch | Target in the list of allowed tenants | Target is served | Result |
|---|---|---|---|---|
| yes | – | – | yes | Selection: runs in the target, with the roles of the membership there |
| no | yes | yes | yes | Privileged switch: runs in the target, with the own roles |
| no | no | – | – | silently ignored, runs in the own tenant |
| no | yes | no | – | CIAS does not switch; the persistence refuses on the first access to tenant data |
| – | – | – | no | 403 cias.authentication.tenant-not-served |
The list of allowed tenants comes from the token: the own tenant, all own organizations and all entries from the user attribute allowedTenants. A header cannot extend it.
The variants
When: Ben is a member of nordbau and suedlogistik and sends tenant: suedlogistik.
-
1Filter chainResolution:
suedlogistikis an own organization → selected -
2Filter chainGate for
suedlogistik: yes -
3Filter chainRoles from the membership in
suedlogistik
Result: No special role needed. Being a member of two companies is ordinary.
When: A support person with tenant acme, role allowed-tenant-context-switch and globex in allowedTenants sends tenant: globex.
-
1Filter chainResolution gives
acme, gate says yes, roles foracme -
2Filter chainRole present, target allowed → tenant :=
globex -
3Filter chainGate for
globex: yes
Result: The person works in globex with their own roles from acme. They do not get roles that someone has in globex.
When: The role is missing, the target is not an own organization.
The wish is not applied. The request runs in the own tenant, without an error and without a hint in the response.
Result: See the pitfall "The silent case" below.
When: The switch would be allowed, but globex is suspended, closed or unknown.
The second gate refuses. Administrators are not exempt either: a suspended tenant is suspended for everyone.
Result: 403 cias.authentication.tenant-not-served. You manage a suspended tenant through the administration API, not through the switch.
When: The request carries the header user: 3f2a….
With the realm role allowed-user-context-switch, the person's ID and name in the RequestContext become those of the target person. The tenant stays. Roles and attributes stay those of the logged-in person; with the header user-roles: target they become the target person's. Without the role, for an unknown person, or for a person who does not belong to the request's tenant, CIAS answers with 403 cias.authentication.user-switch-denied. If the target person has not consented to the switch, with 403 cias.authentication.user-switch-not-consented.
Result: Each switch needs its own realm role. If both headers come together, the tenant is switched first, and the target person must belong to the target tenant. More in User switch by header.
What is known about the tenant after a privileged switch
Code in a module can ask CIAS not only for the key of the current tenant but also for its description: kind and organization. After a privileged switch there is no such description. The resolution described the starting tenant, not the target. CIAS then returns only the key and an empty description, instead of giving the code information about the wrong customer.
Pitfalls
Where to go next
- The CDMS view: Tenant switch by header
- An example from start to finish: Support looks into a tenant
- Determine the tenant of a request