What this is about
Ben from the tenant nordbau reports: “I cannot see my orders any more.” Lena works in platform support. Her own tenant is codamic, and she is not a member of nordbau. She should still be able to look, without the customer having to create an account for her.
There are two headers a client can send for this:
tenant: the request runs in a different tenant.user: the request runs in the name of a different person. With the additional headeruser-roles: target, also with that person’s roles.
Anybody can set both headers, so it is not the header that decides but the signed token. Each of the two switches has its own realm role, that is, a role that applies in Keycloak across the whole platform and that no organization can grant.
What has to be set up first
| What | Where | Why |
|---|---|---|
realm role allowed-tenant-context-switch | directly in Keycloak on Lena’s account | allows a switch into a foreign tenant at all |
nordbau in the attribute allowedTenants | on Lena’s account | says which tenants she may reach |
realm role allowed-user-context-switch | directly in Keycloak | only needed if Lena should also switch the person |
Ben’s consent for Lena in nordbau | Ben approves Lena’s request or gives it himself, see below | without it, every user switch to Ben ends with 403 user-switch-not-consented |
Ben is a member of nordbau | organization nordbau, or Ben’s attribute tenant or allowedTenants | there is no switch to a person outside the target tenant |
| lookup of the target person | only if CDMS and CIAS run separately, see User switch by header | without it, every user switch ends with 403 |
| business roles for the models she wants to look into | in the role catalog, granted to Lena | without them CDMS refuses every model |
The two switch roles are in no role catalog and are not granted through CIAS. More on this under The platform’s realm roles.
Beforehand: Ben consents
The role allowed-user-context-switch allows Lena to switch the person at all. To whom is decided by the target person. Ben has to consent to Lena switching, for nordbau, for the mode and for a limited time.
-
1Client→CIASLena sends
POST /cias/me/switch-requestswithtarget: 3f2a…,mode: target,validUntil(say, in two days) andreason: Ticket 4711. Her token runs in the tenantnordbaufor this. -
2CIAScreates the request and sends Ben a mail: who is asking, with whose permissions, until when and why
-
3Client→CIASBen approves in his portal:
POST /cias/me/switch-consents/{id}/approveResult: From now on, until the end, Lena may work on Ben's behalf, with his roles. On the first switch Ben gets one more mail.
Ben can revoke the consent at any time. Lena’s very next request is then refused. All rules are under The target person’s consent.
One request, step by step
Lena’s tool sends a perfectly normal request to CDMS, plus the two headers:
POST /api/rest/order/query
Authorization: Bearer <token of Lena, tenant codamic>
tenant: nordbau
user: 3f2a…
{ "response": ["id", "orderNr", "price"] }The search runs in the database of nordbau,
with Lena's roles and Ben's user id.
With an additional "user-roles: target" it would run with Ben's roles.sequenceDiagram
participant C as Client
participant F as Filter chain
participant G as Tenant gate
participant D as CDMS
participant DB as Database nordbau
C->>F: token (codamic) + tenant nordbau + user 3f2a…
F->>F: read headers, then check the token
F->>F: resolve: nordbau is not an own organization → codamic
F->>G: Is codamic served?
G-->>F: yes
F->>F: determine roles and attribute values for codamic
F->>F: role present, nordbau allowed → tenant := nordbau
F->>G: Is nordbau served?
G-->>F: yes
F->>F: determine Lena's values per tenant for nordbau again
F->>F: role present, Ben known, member of nordbau
F->>F: consent from Ben for Lena in nordbau? yes → person := Ben
F->>D: pass the request on
D->>DB: search, with Lena's roles and Ben's id
DB-->>C: rows
Two things about this order stand out, and both are deliberate:
- Roles are determined before the switch, for Lena’s originating tenant, and keep applying in the target unchanged. That is why nobody carries rights into a tenant switch that were granted in the target. Lena’s values per tenant, by contrast, are determined again for
nordbau, because they say what she may see there. The only exception is the user switch withuser-roles: target: then Ben’s roles and values apply, as they would appear in his own token fornordbau. - The user switch comes last, after the tenant switch and the second question to the gate. That is why Ben has to be a member of
nordbau, not ofcodamic. - The gate is asked twice: once for the tenant from the token, once for the target. A suspended tenant stays suspended for support too.
What Lena sees and what she does not
- the rows from the database of
nordbau - every model her own roles are sufficient for
- the history of an object, if her roles cover the read role and the history role
- with a user switch: the rows that belong to Ben in user models
- with
user-roles: target: everything Ben's roles and attribute values are sufficient for
- without
user-roles: target: roles somebody holds innordbau, she gets none of them - fields and models her own roles are not sufficient for
- without a user switch: other people's rows in user models, because the owner filter works with her id
- the description of the tenant, that is its kind and organization: after a privileged switch CIAS only returns the key
Attribute values are values on a person that CDMS uses to sift rows, for example regions. Unlike the roles, values per tenant are determined again for nordbau after the switch; all others come from Lena’s token. So an attribute filter sifts in nordbau with Lena’s values for nordbau, not Ben’s, unless user-roles: target is set. See One value per person or per tenant and Attribute filters.
The variants
When: Lena sends tenant: nordbau and no user header.
She works in the database of nordbau with her own roles and her own id. In tenant models she sees the customer's data; in user models she sees nothing, because no row there belongs to her.
Result: The right way to look at a customer's data.
When: Lena sends tenant: nordbau and user: 3f2a….
Each switch needs its own realm role. The tenant is switched first, then the person. Id and name are then Ben's, and the owner filter works with Ben's id. Roles and attribute values stay Lena's.
Result: The way to see a person's own data.
When: As above, plus user-roles: target.
Lena gets Ben's realm roles, business roles, groups and attribute values, completely, including ones she does not have herself. If Ben has roles in the organization nordbau, these replace his global ones, as in his own token. CIAS fetches per-tenant values for Ben.
Result: The way to reproduce what Ben sees.
When: allowed-user-context-switch is missing from the token, the id is unknown, or Ben does not belong to nordbau.
The user switch is not silently skipped. The whole request is refused; why exactly is only in the log. If CIAS cannot look up who Ben is at all, the key is user-switch-unavailable.
Result: 403 cias.authentication.user-switch-denied.
When: Role present, Ben a member of nordbau, but no valid consent for Lena: never given, expired, revoked, for another tenant, or only for own roles while user-roles: target is sent.
The request is refused before anything is switched. The key is a separate one, so that Lena's tool can offer to ask Ben for consent.
Result: 403 cias.authentication.user-switch-not-consented.
When: allowed-tenant-context-switch is missing from the token.
The header is silently ignored. There is no error and no hint in the response: the request keeps running in codamic.
Result: Lena sees her own tenant's data and takes it for the customer's.
When: nordbau is not in Lena's list of allowed tenants.
CIAS does not switch. The persistence layer sees the refused wish at the first access to a tenant model.
Result: 403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED. If the request only touches system models, there is no error.
When: The switch would be allowed, but nordbau is suspended, closed or expired.
The second gate refuses, with no exemption for support.
Result: 403 cias.authentication.tenant-not-served. See A customer cancels.
When: Lena is a member of the organization nordbau.
Then the header is not a privileged action but a selection: she needs no switch role, and her roles in nordbau apply.
Result: Different path, different outcome. See Switch between tenants.
| Header | realm role present | target allowed or known | Result |
|---|---|---|---|
tenant | no | – | silently ignored, the request stays in the own tenant |
tenant | yes | target in the list of allowed tenants | the tenant of the request becomes the target, roles stay the own ones |
tenant | yes | target not in the list | no switch; 403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED at the first access to tenant data |
user | no | – | 403 cias.authentication.user-switch-denied |
user | yes | person unknown or not a member of the request's tenant | 403 cias.authentication.user-switch-denied |
user | yes | person known and a member, but no matching consent | 403 cias.authentication.user-switch-not-consented |
user | yes | person known, a member, and the consent fits | id and name become Ben's; roles Lena's, or Ben's with user-roles: target |
What a switch leaves behind
-
1CIASThe tenant switch publishes no event. For the user switch, the request, the consent and the first use of the consent appear as
SwitchConsentEvent.*in the CIAS audit, not every single request -
2CDMSPlain reading leaves no revision. Only a change writes one
-
3CDMS→DatabaseIf Lena changes something, the revision records user id, user name, IP address and browser of the requestResult: After a user switch the revision holds Ben's user id and name, plus Lena as the acting person. In the history her name appears in
actingUsername.
More on this under What a revision records and Which events are logged.
Traps
Next
- Switch between tenants and the view from CDMS: Tenant switch by header
- User switch by header
- Determine the tenant of a request and Admit the tenant (tenant gate)
- Effective roles: global or in the tenant
- The whole path of a request: From login to the data