CodamAIDocs
Topicdone

Where the tenant of a request comes from

The tenant is in the token. This page explains how it is read and what happens without a tenant.

Variants
from the organization in the tokenfrom the tenant attributeselection among several organizationsseveral organizations without selection → 403organization and attribute contradict each other → 403no tenant in MULTI → 403, behind it 400SINGLE ignores it

What this is about

On every request CDMS has to know which tenant it runs for. Only then does it find the right database. The tenant comes from the token that Keycloak issued and signed. The client does not send it as a separate field, and CDMS does not take it from any field in the data either.

The CIAS filter chain reads the token before CDMS does anything. It stores the result in the RequestContext. That is a store that lives for exactly one request and is cleared at the end. Everything later reads the tenant only from there.

The path of the tenant

flowchart LR
    T["Token<br/>organization: acme<br/>tenant: acme<br/>allowedTenants: …"] --> F["CIAS filter chain<br/>reads and checks"]
    H["Header tenant<br/>(only a wish)"] -.-> F
    F --> RC["RequestContext<br/>tenant: acme<br/>allowed tenants: acme, …"]
    RC --> P["CDMS persistence<br/>chooses the database"]
    P --> DB[("Database acme")]

Where the tenant is in the token

Keycloak can write the tenant into the token in two ways:

  • Organization: The person is a member of an organization in Keycloak. The token carries it in the organization claim. The alias of the organization, its short name in Keycloak, is the tenant key. This is how tenants come about that CIAS creates at runtime.
  • Attribute tenant: The person has a user attribute tenant holding the tenant key. That is the permanently assigned tenant.

A tenant key consists only of lowercase letters, digits and hyphens, for example acme or stadtwerke-nord. It is also the name of the tenant’s database.

How tenants and organizations come about in CIAS is explained in Static and dynamic tenants.

Which tenant applies

How CIAS determines the tenant from the token
Organizations in the tokenAttribute tenantHeader tenant names an own organizationTenant of the request
nonemissing–no tenant
noneacme–acme
one or morenames none of them–403 cias.authentication.tenant-unresolved: the two sources contradict each other
several–yes, e.g. globexglobex: selection among the person's own organizations
severalnames one of themnothe organization from the attribute
exactly onemissingnothat one organization
severalmissingno403 cias.authentication.tenant-unresolved: unclear whose data is meant

Read the rows from top to bottom. There is no default tenant CIAS falls back to. A wrong tenant would be worse than no answer: it would show one customer the data of another.

What ends up in the RequestContext

Once the tenant is determined, CIAS also checks whether it is served. Then it writes into the RequestContext:

EntryContent
Tenantthe tenant key just determined
Allowed tenantsthe own tenant, all own organizations and all entries from the user attribute allowedTenants
Switch wishthe value of the tenant header, if present
User, roles, attributesas described in The three levels of security

The list of allowed tenants limits where a tenant switch can lead at all. It comes from the signed token, never from a header. The attribute allowedTenants may have several values or be a comma-separated list.

Variants

Tenant present, missing or ignored

When: MULTI, the token names exactly one tenant

  1. 1
    Client→CDMS
    sends the request with Authorization: Bearer …
  2. 2
    CIAS
    checks the token and determines the tenant acme
  3. 3
    CIAS
    asks whether acme is served: yes
  4. 4
    CDMS→Database
    reads and writes tenant models in the database acme

Result: The request runs entirely in the tenant acme.

When: The token belongs to a real person or a service account but names neither an organization nor the attribute tenant.

  1. 1
    CIAS
    checks the token: valid, but without a tenant
  2. 2
    CIAS→Client
    403 cias.authentication.tenant-required, before CDMS even sees the request

Result: The error says: this person was never assigned a tenant. That is fixed in Keycloak or CIAS, not in the client.

When: A request still reaches the persistence without a tenant, for example from custom code.

  1. 1
    CDMS
    wants to read or write a tenant model and finds no tenant
  2. 2
    CDMS
    400 CDMS_TENANT_REQUIRED, no fallback to the system database

Result: System models are not affected; they need no tenant.

When: CODAMAI_PERSISTENCE_TENANT_MODE=SINGLE

CIAS does not evaluate the organization or the attribute tenant at all. The request runs without a tenant, the list of allowed tenants stays empty, a tenant header has no effect. Everything ends up in the one database.

Result: A token with a tenant and one without behave the same in SINGLE.

The error responses of the filter chain

When the filter chain refuses, the response comes from CIAS and not from CDMS. It therefore has its own short format:

Request
POST /api/rest/crm/customer/query
Authorization: Bearer <token with two organizations, no selection>
Response
HTTP 403
{ "error": "cias.authentication.tenant-unresolved", "message": "request refused" }
KeyMeaning
cias.authentication.tenant-unresolvedThe token names tenants, but not exactly one.
cias.authentication.tenant-requiredThe token names no tenant at all, although the installation is MULTI.
cias.authentication.tenant-not-servedThe tenant is unambiguous but is not served. See Is the tenant served?

An invalid or expired token is a different case: that results in 401, see Access without a token.

Pitfalls

Where to go next

Sources in the code and the knowledge base
  • CIAS/cias-authentication – JwtSessionFilter (read tenant/user headers before the token, tenantMissing, refuse), TokenParser (admit, buildAllowedTenants), OrganizationTenantResolver, CiasTokenProperties (tenant, allowedTenants, organization)
  • CIAS/cias-authentication – TenantResolution (RESOLVED, NONE, AMBIGUOUS, CONFLICT), RequestAdmission (tenant-unresolved, tenant-not-served, tenant-required)
  • commons – RequestContext (userTenant, allowedTenants, userTenantSwitchRequest)
  • commons-persistence – DatabaseRequestContext.resolveTenant, PersistenceErrorCode (CDMS_TENANT_REQUIRED 400)
  • documentation/30-daten-und-persistenz/01-mandantentrennung.md
Search