CodamAIDocs
Topicdone

Switch between tenants

Selecting among your own organizations without a special role, privileged switch with a role, user switch, and the fact that the tenant is admitted again after every switch.

Variants
select own organizationprivileged switchwithout role → silently ignoredtarget suspended → 403user switch

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

What the header tenant does (operating mode MULTI)
Target is an own organizationRealm role allowed-tenant-context-switchTarget in the list of allowed tenantsTarget is servedResult
yes––yesSelection: runs in the target, with the roles of the membership there
noyesyesyesPrivileged switch: runs in the target, with the own roles
nono––silently ignored, runs in the own tenant
noyesno–CIAS does not switch; the persistence refuses on the first access to tenant data
–––no403 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

Switch and selection in detail

When: Ben is a member of nordbau and suedlogistik and sends tenant: suedlogistik.

  1. 1
    Filter chain
    Resolution: suedlogistik is an own organization → selected
  2. 2
    Filter chain
    Gate for suedlogistik: yes
  3. 3
    Filter chain
    Roles 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.

  1. 1
    Filter chain
    Resolution gives acme, gate says yes, roles for acme
  2. 2
    Filter chain
    Role present, target allowed → tenant := globex
  3. 3
    Filter chain
    Gate 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

Sources in the code and the knowledge base
  • CIAS/cias-authentication – ContextSwitch (allowed-tenant-context-switch, isAllowedTarget), OrganizationTenantResolver (selection), TokenParser.admit (order: resolution, gate, roles, context, switch, second gate), TokenParser.switchUser (allowed-user-context-switch, after the tenant switch), JwtSessionFilter (header tenant and user before the token)
  • CIAS/cias-authentication – CurrentTenantProviderImpl (description of the tenant only for the same key), ResolvedTenantHolder
  • CIAS/cias-authentication/docs/adr – ADR-006 (section 3), ADR-021 (section 4)
  • commons-persistence – DatabaseRequestContext.requireAllowedTenant
Search