CodamAIDocs
Topicdone

Tenant switch by header

How an authorized person works for another tenant with the tenant header, and why a switch without the role is silently ignored.

Variants
selection among own organizationsprivileged switchwithout role → silently ignoredtarget not allowed → 403target suspended → 403operating mode SINGLE

What this is about

Normally a request works in the tenant from the token. Sometimes someone has to work in another tenant on purpose: a person who belongs to two companies, or support staff helping a customer. For this the client sends the tenant header with the desired tenant key:

Request
POST /api/rest/crm/customer/query
Authorization: Bearer <token>
tenant: globex
{ "response": ["id", "name"] }
Response
The search runs in the database of globex,
provided the switch is allowed.

The header is not trustworthy, because any client can set it. It is therefore only a wish. Whether it takes effect is decided by information from the signed token.

There are two kinds of switch:

  • Selection: The person is a member of the target tenant, that is of this organization. No special role is needed for that.
  • Privileged switch: The person is not a member of the target tenant. This needs the realm role allowed-tenant-context-switch and the target must be in the person’s list of allowed tenants. A realm role is a role in Keycloak that applies to the whole platform, not just to one application.

The decision table

What the tenant header does (operating mode MULTI)
Target is an own organizationTarget in the list of allowed tenantsRealm role allowed-tenant-context-switchTarget is servedResult
yes––yesrequest runs in the target tenant (selection)
noyesyesyesrequest runs in the target tenant (privileged switch)
noyesno–header is silently ignored, request runs in the own tenant
nono––no switch; the first access to a tenant model ends with 403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED
–yesyesno403 cias.authentication.tenant-not-served

”–” means: does not matter in this row.

The flow

sequenceDiagram
    participant C as Client
    participant F as CIAS filter chain
    participant G as Tenant gate (CIAS)
    participant P as CDMS persistence
    participant DB as Database globex
    C->>F: Request with token (tenant acme) and header tenant: globex
    F->>F: Determine tenant from the token: acme
    F->>G: Is acme served?
    G-->>F: yes
    F->>F: Role allowed-tenant-context-switch? globex allowed?
    F->>F: Tenant of the request := globex
    F->>G: Is globex served?
    G-->>F: yes
    F->>P: Pass the request on
    P->>P: globex in the list of allowed tenants? yes
    P->>DB: read and write
    P-->>C: Response

Two checks stand out:

  • CIAS asks the tenant gate twice: once for the tenant from the token and, after the switch, once more for the target. A suspended tenant stays suspended even for someone who switches into it, administrators included.
  • The persistence checks the list of allowed tenants a second time, independently of CIAS. Two separate checks protect the same fact.

Variants

The variants of the switch

When: The person is a member of the organizations acme and globex and sends tenant: globex.

  1. 1
    CIAS
    recognizes globex as an own organization and selects it
  2. 2
    CIAS
    determines roles and attributes for globex
  3. 3
    CDMS→Database
    works in the database globex

Result: No special role needed. In globex the person has exactly the roles they have there as a member.

When: A support person with tenant acme, role allowed-tenant-context-switch and globex in the attribute allowedTenants sends tenant: globex.

  1. 1
    CIAS
    determines acme from the token, gate says yes
  2. 2
    CIAS
    role present, target allowed → tenant of the request becomes globex
  3. 3
    CIAS
    asks the gate for globex: yes
  4. 4
    CDMS→Database
    works in the database globex

Result: The person takes their own roles and attributes along. They do not get roles that someone has in globex.

When: globex is in the list of allowed tenants, the role is missing.

  1. 1
    CIAS
    role missing → the wish is not applied
  2. 2
    CDMS→Database
    works in the database acme

Result: No error, no hint in the response. The data comes from the own tenant.

When: globex is not in the list of allowed tenants, with or without the role.

  1. 1
    CIAS
    target not allowed → the wish is not applied
  2. 2
    CDMS
    sees the refused wish on the first access to a tenant model
  3. 3
    CDMS
    403 CDMS_TENANT_SWITCH_NOT_AUTHORIZED

Result: If the request only accesses system models, there is no error, because they need no tenant.

When: The switch would be allowed, but globex is suspended, closed or unknown.

  1. 1
    CIAS
    switches to globex and asks the gate
  2. 2
    CIAS→Client
    403 cias.authentication.tenant-not-served

Result: The request does not reach CDMS. You manage a suspended tenant through the CIAS administration API, not through the switch.

When: CODAMAI_PERSISTENCE_TENANT_MODE=SINGLE

There are no tenants and the list of allowed tenants is empty. The tenant header does nothing, not even with the role, and does not lead to an error either.

Result: Everything stays in the one database.

Pitfalls

Where to go next

Sources in the code and the knowledge base
  • CIAS/cias-authentication – JwtSessionFilter (header tenant as userTenantSwitchRequest), OrganizationTenantResolver (selection among own organizations), ContextSwitch (allowed-tenant-context-switch, isAllowedTarget), TokenParser.admit (second gate check after the switch, roles stay)
  • commons-persistence – DatabaseRequestContext.requireAllowedTenant (deviation A-3), PersistenceErrorCode.CDMS_TENANT_SWITCH_NOT_AUTHORIZED
  • CIAS/cias-authentication – RequestAdmission (tenant-not-served)
  • documentation/30-daten-und-persistenz/01-mandantentrennung.md (step 2)
Search