What this is about
Every authenticated request runs under at most one tenant. The CIAS filter chain decides which one, before any module sees the request. CIAS calls this step resolving. It only answers the question “which tenant?”. Whether this tenant may work is checked afterwards by the tenant gate.
The resolution reads three pieces of information:
- the organizations in the token (claim
organization), that is, the dynamic tenants the person is a member of - the attribute
tenantin the token, the permanently assigned tenant - the header
tenantof the request, a wish of the client
The rules
The rules are checked from top to bottom. The first one that matches decides.
flowchart TB
S([Token read]) --> O{"Organizations<br/>in the token?"}
O -- "none" --> A{"Attribute tenant?"}
A -- "missing" --> NONE["NONE<br/>without tenant"]
A -- "set" --> RS["RESOLVED<br/>static tenant"]
O -- "one or more" --> C{"Attribute tenant names<br/>none of them?"}
C -- "yes" --> CON["CONFLICT<br/>403"]
C -- "no or missing" --> H{"Header tenant names<br/>one of them?"}
H -- "yes" --> R1["RESOLVED<br/>the selected one"]
H -- "no" --> T{"Attribute tenant<br/>names one of them?"}
T -- "yes" --> R2["RESOLVED<br/>the one from the attribute"]
T -- "no" --> E{"exactly one?"}
E -- "yes" --> R3["RESOLVED<br/>that one"]
E -- "no" --> AMB["AMBIGUOUS<br/>403"]
| Organizations in the token | Attribute tenant | Header tenant | Result |
|---|---|---|---|
| none | missing | – | NONE: the request has no tenant |
| none | stadtwerke-nord | – | RESOLVED: stadtwerke-nord, static |
nordbau, suedlogistik | globex | – | CONFLICT: the attribute names none of the person's own organizations |
nordbau, suedlogistik | – | suedlogistik | RESOLVED: suedlogistik, selected |
nordbau, suedlogistik | nordbau | missing or not an own one | RESOLVED: nordbau, from the attribute |
only nordbau | missing | missing or not an own one | RESOLVED: nordbau |
nordbau, suedlogistik | missing | missing or not an own one | AMBIGUOUS: unclear whose data is meant |
The contradiction (CONFLICT) is checked before the selection by header. Otherwise a wrongly set up person who has an attribute tenant and is also a member of organizations could be steered into any of their organizations by header.
The four results
| Result | Meaning | What the filter chain does |
|---|---|---|
RESOLVED | exactly one tenant | asks the tenant gate and writes the tenant into the RequestContext |
NONE | the token names no tenant at all | in MULTI: 403 cias.authentication.tenant-required, except for paths under /cias/**. In SINGLE and without a set operating mode: the request runs without a tenant |
CONFLICT | attribute and organizations contradict each other | 403 cias.authentication.tenant-unresolved |
AMBIGUOUS | several organizations, no selection | 403 cias.authentication.tenant-unresolved |
In SINGLE the resolution does not happen at all: every token counts as a token without a tenant there, even one with organizations. See In SINGLE the tenant in the token does not count.
NONE on its own is not a refusal. A token without a tenant has always meant “without tenant”, and a request without a tenant may do nothing that needs a tenant. It is refused only when the installation separates tenants (MULTI). The paths under /cias/** then stay reachable, so the person can see their profile and an administrator can add the missing tenant.
The two keys tenant-unresolved and tenant-required separate two errors that are fixed in different places: “you did not select unambiguously” is fixed by the client, “you were never assigned a tenant” is fixed by an administrator in CIAS.
Selection is not a switch
The header tenant serves two purposes, and the resolution only handles the first one:
- Selection: The header names an organization the person is a member of themselves. This is ordinary and needs no special role. The resolution selects this organization, and the person’s roles in this organization apply.
- Switch: The header names a tenant the person is not a member of. This is privileged and happens only after resolution and gate. See Switch between tenants.
So that the selection takes effect in time, the filter chain reads the headers before the token. After all, the selected organization decides which roles the person has.
What is in the RequestContext after the resolution
The RequestContext is the store that lives for exactly one request. The resolution fills it after the gate has agreed:
| Entry | Content |
|---|---|
| Tenant | the resolved key, empty for NONE |
| Allowed tenants | the resolved tenant, all own organizations and all entries from the attribute allowedTenants |
| Roles and attributes | matching the resolved tenant, see Effective roles: global or in the tenant |
| Description of the tenant | kind (STATIC/DYNAMIC) and the organization behind it, for code that needs more than the key |
The own organizations are part of the list of allowed tenants. Otherwise the persistence would refuse a person who selects their second organization.