What this is about
Clara consults for two customers: nordbau and suedlogistik. She has one account and one password. Still, none of her requests may mix the data of the two customers.
CIAS solves that by having every single request run under exactly one tenant. Which one it is, is decided afresh before every request – and the roles and the attribute values that apply to that one request hang off it.
The picture
flowchart TB
K["One account in Keycloak<br/>clara@example.example"]
K --> T["One token:<br/>organization: nordbau, suedlogistik<br/>roles per organization<br/>realm roles<br/>profile attributes"]
T --> R{"Which tenant<br/>for this request?"}
R -- "header tenant: nordbau" --> N["request in nordbau<br/>roles from nordbau<br/>values for nordbau"]
R -- "header tenant: suedlogistik" --> S["request in suedlogistik<br/>roles from suedlogistik<br/>values for suedlogistik"]
N --> ND[("database nordbau")]
S --> SD[("database suedlogistik")]
How Clara ends up in two tenants
There are two shapes, and they behave differently:
| Two organizations | One static tenant, more allowed | |
|---|---|---|
| How she belongs | member of both organizations | attribute tenant names her tenant, allowedTenants names further targets |
| Where it sits in the token | claim organization with both aliases | claims tenant and allowedTenants |
| What she has to do to change | send the header tenant – no special role needed | the header tenant and the realm role allowed-tenant-context-switch |
| Which roles apply | the roles of her membership in the chosen tenant | her own roles – she carries them into the target tenant |
| How she got there | one invitation per tenant, which she redeemed herself | an administrator set the attributes |
The first is called selection, the second a privileged switch. The difference is not cosmetic: selection happens during tenant resolution, the switch only after it. And the roles are determined in between.
How the choice is made
The filter chain reads the header tenant before the token, because the chosen organization decides which roles apply. Then it works through the rules in order.
| organizations in the token | header tenant | realm role allowed-tenant-context-switch | Result |
|---|---|---|---|
nordbau, suedlogistik | suedlogistik | – | selection: request in suedlogistik, with Clara's roles there and the values CIAS holds for her in suedlogistik |
nordbau, suedlogistik | missing | – | 403 cias.authentication.tenant-unresolved – with two organizations and no choice it is unclear whose data is meant |
only nordbau | missing | – | request in nordbau, there is nothing to choose |
none, attribute tenant = nordbau | suedlogistik, and it is in allowedTenants | present | privileged switch: request in suedlogistik, but with her own roles from nordbau |
none, attribute tenant = nordbau | suedlogistik | missing | silently ignored: the request runs in nordbau, without an error and without a hint |
nordbau, suedlogistik | – | – | target suspended, closed or unknown: 403 cias.authentication.tenant-not-served |
Every rule in detail: Determine the tenant of a request and Switch between tenants.
Which roles apply in each tenant
Roles can sit in two places in the token: globally and inside an organization. From those CIAS builds the effective roles on every request.
In Clara's token:
global (resource_access): report-read
organization nordbau: order-read, order-edit
organization suedlogistik: order-read
realm_access: userEffective in nordbau: order-read, order-edit (+ realm: user)
Effective in suedlogistik: order-read (+ realm: user)report-read falls away in both tenants. That is deliberate: if Clara has any role in her active, dynamic tenant, the roles of that tenant replace the global client roles. Nobody granted a globally granted role for this one customer, so it does not apply there. Realm roles, by contrast, always apply, in every tenant.
With a privileged switch it works differently: there Clara carries her own roles into the target. Roles somebody has in the target tenant she does not get. Details: Effective roles: global or in the tenant.
Which attribute values apply in each tenant
An attribute is a fact about a person that reaches the token as a claim and that CDMS uses to filter rows. On declaration, every module says what the attribute is about:
| Binding | Meaning | Where the value sits |
|---|---|---|
USER | one value per person, the same in every tenant | on the account in Keycloak, arrives through the token |
USER_IN_TENANT | one value per person and tenant | in CIAS, per person and tenant |
For an attribute with USER_IN_TENANT, CIAS asks on every request for the values it holds for Clara in the active tenant.
-
1CIASreads the attributes from the token, as they stand on the account
-
2CIASfetches the values it holds for this person in this tenant
-
3CIASfor every key it holds values for, those replace the token's value. They are never merged
-
4CIASwhere CIAS holds no values for a key in this tenant, the token's value stays
-
5CDMSfilters the rows with whatever ends up in the RequestContextResult: Same person, same token, different rows per tenant
On the account (token): region = nord, sued
In CIAS, nordbau: region = nord
In CIAS, suedlogistik: region = suedEffective in nordbau: region = nord
Effective in suedlogistik: region = suedIf CIAS cannot fetch the values and has no buffered value either, it refuses the request instead of falling back to the token’s value: 403 cias.authentication.tenant-not-served. CIAS keeps the answers per person and tenant for 30 seconds.
What does not differ per tenant
| What | Why |
|---|---|
| account and password | the address is the account. There is exactly one |
| realm roles | they apply to the whole platform. No organization can add to them |
| groups | group membership hangs off the account, not off the tenant |
profile attributes (USER) | one value per person, the same in every tenant |
| home tenant in the user record | CIAS keeps one home tenant per person. Further memberships live in Keycloak and arrive through the token |
language (locale) | a preference of the person |
The home tenant in the CIAS user record is therefore no statement about which tenant a request runs in. That is always decided by the token. See The user record.
The path of a request, shortened
-
CIASRead the headerThe header is read before the token, because it co-decides the roles
-
CIASResolveIs
suedlogistikone of her own organizations? Then it is the tenant of the request↳ no 403cias.authentication.tenant-unresolved -
CIASTenant gateIs
suedlogistikserved, that isACTIVEand inside its validity window?↳ no 403cias.authentication.tenant-not-served -
CIASRoles and attributesroles from the organization
suedlogistik, attribute values forsuedlogistik -
CDMSPermission checkDoes an effective role allow this model?↳ no 403
-
CDMSDatabaseIn MULTI, the database of
suedlogistik - Rows from suedlogistik, filtered with Clara's values for suedlogistik
The full path is described by From login to the data.