CodamAIDocs
Topicdone

What happens with the token on every request

The filter chain step by step: read the header, validate, exchange, read the identity, resolve the tenant, admit the tenant, build the roles, perform the switch, clean up.

Variants
valid tokenno token → 403invalid, expired or foreign token → 401token exchange refused → 401Keycloak unreachable → 503MULTI without tenant → 403tenant not unique or not served → 403user switch refused → 403SINGLE: the tenant in the token does not count

What this is about

Every request to CDMS or CIAS first runs through the filter chain of CIAS. The filter chain is code that runs before every endpoint. It answers three questions before the actual application does anything at all:

  1. Who is making the request?
  2. In which tenant does the request run, and may this tenant be served?
  3. Which roles and attributes apply to this request?

It stores the result in the RequestContext: a store that applies to exactly this one request. CDMS looks there to see who is making the request. It never asks the token itself again.

The stations

A request with the header Authorization Bearer
  1. Filter chain
    Open path?
    Is the path allowed without login, such as the public registration? Then the request may pass even without a token.
  2. Filter chain
    Token present?
    Is there a token in the Authorization header?
    ↳ no 403 (on protected paths)
  3. Filter chain
    Validate token
    Is the signature valid with a key of the realm and the token not expired? Does it come from the configured issuer, is it an access token, and was it issued for this application?
    ↳ no 401 with WWW-Authenticate: Bearer error="invalid_token"
  4. CIAS
    Exchange token
    Keycloak exchanges the token for one for the CIAS client
    ↳ no Keycloak refuses: 401 cias.authentication.token-rejected. Keycloak unreachable: 503 cias.authentication.identity-provider-unavailable
  5. CIAS
    Resolve tenant
    Which tenant does the request come from? Is that unique?
    ↳ no 403 cias.authentication.tenant-unresolved
  6. CIAS
    Admit tenant
    Does the tenant exist, and is it active and valid?
    ↳ no 403 cias.authentication.tenant-not-served
  7. CIAS
    Roles and attributes
    Build the effective roles and attributes for this tenant
    ↳ no Attribute values cannot be fetched: 403 cias.authentication.tenant-not-served
  8. CIAS
    Switch
    Is the tenant header set and covered by a realm role? Otherwise it is silently ignored. Then the user header: realm role present, target person known and in the request's tenant, consent of the target person present?
    ↳ no User switch: 403 cias.authentication.user-switch-denied, user-switch-not-consented or user-switch-unavailable
  9. CIAS
    Tenant required?
    Operating mode MULTI, person known, but no tenant?
    ↳ no 403 cias.authentication.tenant-required
  10. The application gets the request with a filled RequestContext

At the end, after the response, the filter chain clears the RequestContext again. The next request on the same thread starts empty.

Each station in short

StationWhat happensMore on this
Open pathSome paths need no login: OPTIONS requests from the browser and, if switched on, /cias/registration/**. /v3/api-docs and /swagger-ui need no token, but a user name and password.Access without a token
Validate tokenThe signature is checked against the public keys of the realm, plus expiry and start of validity. Also: iss has to be exactly the configured issuer, typ has to be Bearer (an ID token does not count), and the token has to name the application’s client in aud or come from it (azp). Keycloak is not asked for this.The identity provider: one issuer
Exchange tokenCIAS exchanges the token at Keycloak for one for its own client. Only this one contains all roles, organizations and attributes.Token exchange
Read identityFrom the exchanged token, CIAS reads the user ID, name, roles, organizations and attributes.What is read from the token
Resolve tenantfrom the organization claim (dynamic) or the tenant attribute (static).Determine the tenant of a request
Admit tenantThe tenant gate asks whether the tenant is served. The answer is remembered for 30 seconds.Admit the tenant
Roles and attributesGlobal roles or roles in the tenant, attribute values per tenant.Effective roles
SwitchFirst the tenant switch, then the user switch via header, each only with its realm role. A user switch without the role, to an unknown person or to a person outside the tenant is refused.Tenant switch via header, User switch via header

The flow as a sequence

sequenceDiagram
    participant C as Client
    participant S as Filter chain
    participant K as Keycloak
    participant T as Tenant gate
    participant A as CDMS
    C->>S: Request with bearer token
    S->>S: Check signature, expiry, issuer, type, audience
    S->>K: Exchange token (or take from cache)
    K-->>S: Token for the CIAS client
    S->>S: Read identity, resolve tenant
    S->>T: Is tenant acme served?
    T-->>S: yes (remembered for 30 s)
    S->>S: Roles, attributes, switch
    S->>A: Request with RequestContext
    A-->>C: Response
    S->>S: Clear RequestContext

The exits

What can happen to a request

When: Token genuine, not expired, tenant unique and served.

The RequestContext contains the user ID, name, tenant, allowed tenants, realm roles, business roles, groups and attributes. After that, the application checks its own roles.

Result: The request reaches the application.

When: No Authorization header, or without the word Bearer.

Open paths pass. Every other path gets 403, without a CDMS error body.

Result: Log in first. See Access without a token.

When: Expired, broken, signed with a foreign key, from another issuer, an ID token, or issued for another client.

The check fails before CIAS even reads the token. Response 401 cias.authentication.token-rejected with WWW-Authenticate: Bearer error="invalid_token". This also applies on open paths: a broken token is not treated like "no token".

Result: Renew the token and repeat the request. See Renew the token.

When: Keycloak refuses the token exchange or returns no token.

CIAS writes the reason to the log and ends the request with 401 cias.authentication.token-rejected. A request never continues without an identity.

Result: See Token exchange.

When: Keycloak does not answer the token exchange, or answers with a server error (5xx).

503 cias.authentication.identity-provider-unavailable. It is not the token's fault; the same request may succeed later.

Result: Retry later. Operations sees the reason in the log.

When: Operating mode MULTI, the person is known, but the token gives no tenant.

The filter chain refuses with 403 cias.authentication.tenant-required. The paths of CIAS itself under /cias/** are excluded, so the person can at least open their own profile page.

Result: The person is missing an organization or the tenant attribute.

When: The person belongs to several organizations, and nothing says which one is meant. Or the tenant attribute names an organization the person is not a member of.

403 cias.authentication.tenant-unresolved.

Result: The client picks the tenant with the tenant header, see Determine the tenant of a request.

When: The tenant is unknown, suspended, closed, outside its validity, or CIAS cannot be reached and nothing is remembered.

403 cias.authentication.tenant-not-served. All reasons get the same key on purpose, so nobody can find out from the response which customers exist. The log tells them apart.

Result: See Admit the tenant (tenant gate).

When: The request carries the user header, but the realm role allowed-user-context-switch is missing, the target person is unknown or does not belong to the tenant, or user-roles has an invalid value.

403 cias.authentication.user-switch-denied. If the target person has not consented to the switch, 403 cias.authentication.user-switch-not-consented. If CIAS cannot look up who the target person is at all, or cannot check the consent, 403 cias.authentication.user-switch-unavailable. The exact reason is only in the log.

Result: See User switch via header.

When: Operating mode SINGLE: there are no tenants.

Resolving the tenant and the tenant gate are skipped. A tenant in the token is ignored, the list of allowed tenants is empty, and the tenant header has no effect. Roles in the tenant do not apply, only realm roles and the global client roles.

Result: See In SINGLE, the tenant in the token does not count.

The identity provider: one issuer

What “the configured issuer” is measured against is stated in exactly one place: CIAS_ISSUER, the issuer as the token carries it, for example https://sso.example.com/realms/example. CIAS derives the realm, the address of the keys and that of the token exchange from it. Where the service reaches Keycloak by another address than the browser, inside a container network say, CIAS_BACKCHANNEL_URL names that address. The issuer stays the same.

The error response of the filter chain

If the filter chain refuses, the response always looks like this, with the matching status:

Request
GET /api/rest/crm/customer/read/42
Authorization: Bearer eyJ…
Response
403
{ "error": "cias.authentication.tenant-not-served", "message": "request refused" }
Key in errorMeaning
cias.authentication.token-rejected401: token invalid, foreign, or refused by the token exchange
cias.authentication.identity-provider-unavailable503: Keycloak cannot be reached right now
cias.authentication.tenant-unresolvedTenant not unique or contradictory
cias.authentication.tenant-not-servedTenant is not served, or its data cannot be fetched right now
cias.authentication.tenant-requiredOperating mode MULTI, but no tenant
cias.authentication.user-switch-deniedUser switch not allowed: role missing, target person unknown or not in the tenant, invalid user-roles
cias.authentication.user-switch-not-consentedUser switch not consented: the target person has not agreed to this switch, or the consent has expired or been revoked
cias.authentication.user-switch-unavailableUser switch not possible: CIAS cannot check the target person or their consent right now

The format is different from CDMS errors (messageKey). A 403 with error comes from the filter chain, a 403 with messageKey comes from CDMS. See 401, 403, 404.

Next

Sources in the code and the knowledge base
  • CIAS/cias-authentication – SessionConfig (open paths, Http403ForbiddenEntryPoint, oauth2ResourceServer, DefaultBearerTokenResolver), JwtDecoderUtil, CiasTokenValidation, TokenExchangeService, IdentityProviderUnavailableException
  • CIAS/cias-authentication – JwtSessionFilter (doFilterInternal, tenantMissing, isCiasSurface, refuse), TokenParser (tokenParser, admit, switchUser), SwitchTargetLookup, OrganizationTenantResolver, TenantGate, EffectiveRoles, EffectiveAttributes, ContextSwitch, RequestAdmission
  • CIAS/cias-kernel – TenantRequirement
  • CIAS/cias-runtime – SecurityChainEndToEndTest
  • CIAS/cias-authentication/docs/adr – ADR-021, ADR-030, ADR-035, ADR-042, ADR-049
  • CIAS/cias-kernel – CiasIssuer
  • CIAS/cias-integrationtest – AbstractColdStartTest.aTokenForSomebodyElseIsRefused
Search