CodamAIDocs
Topicdone

CIAS as a separate service

CIAS runs as its own service. What the CDMS service then brings along itself, which calls go over HTTP, and with which token.

Variants
tenant check through HTTPattribute lookup through HTTPdeclaration through GET /cias/fetchservice token instead of user tokenown CIAS databaseCIAS does not answer

What this is about

Standalone means: CIAS is a program of its own, with its own address and its own database. The CDMS service and the CIAS service run next to each other and talk over HTTP.

The shipped program for standalone operation is called cias-runtime. It consists of the same modules that run embedded inside a host — the subject area is the same, only the packaging differs.

The counterpart is under CIAS embedded, all differences side by side under Embedded and standalone compared.

The process picture

flowchart LR
    F["Frontend with BFF"]
    subgraph D1["Service 1: CDMS"]
        direction TB
        FK["Filter chain<br/>cias-authentication"]
        TC["cias-tenancy-client"]
        C["CDMS"]
        FK --> C
        FK --> TC
    end
    subgraph D2["Service 2: cias-runtime"]
        CI["CIAS modules<br/>tenancy, user, authorization,<br/>registration, notification, audit"]
    end
    F -- "bearer token<br/>/api/rest/…" --> FK
    F -- "bearer token<br/>/cias/…" --> CI
    TC -- "HTTP + service token<br/>tenant? attributes?" --> CI
    CI -- "HTTP + service token<br/>GET /cias/fetch" --> C
    C --> SDB[("System DB<br/>+ tenant DBs")]
    CI --> CDB[("CIAS database")]
    FK -- "keys, token exchange" --> K[(Keycloak)]
    CI -- "adapter" --> K
    classDef client fill:#475569,stroke:#475569,color:#fff
    classDef cdms fill:#1976d2,stroke:#1976d2,color:#fff
    classDef cias fill:#8e24aa,stroke:#8e24aa,color:#fff
    classDef idp fill:#c2410c,stroke:#c2410c,color:#fff
    classDef db fill:#4d7c0f,stroke:#4d7c0f,color:#fff
    class F client
    class C cdms
    class FK,TC,CI cias
    class K idp
    class SDB,CDB db

Two arrows run between the services, and they run in different directions. That is the thing to remember: CIAS asks CDMS something too.

What the CDMS service brings along

Exactly two jars of CIAS sit in the CDMS service:

The two CIAS building blocks in the CDMS service
cias-authentication
the filter chain
  • checks the token against Keycloak on every request
  • exchanges the token and resolves the tenant
  • contains the tenant gate and the attribute lookup together with their memory
  • runs in the CDMS process in both operating modes
cias-tenancy-client
the way to the CIAS service
  • answers the same two questions as embedded – only over HTTP
  • deliberately tiny: cias-kernel, the JDK's HTTP client, nothing else
  • no cache of its own, no retry
  • does not exist at all when embedded

The choice between the two worlds is one property with no default:

codamai.cias.tenancy.lookup = local    # cias-tenancy,        a method call
codamai.cias.tenancy.lookup = remote   # cias-tenancy-client, HTTP

A service that says nothing about it does not start. That is intended: guessing here would mean inventing an answer to “may this customer be served?”.

Which calls go over HTTP

DirectionCallWhenAnswer
CDMS → CIASGET /cias/lookup/tenants/{key}on every request with a tenant, when nothing is remembered200 {"key":"kunde-a","served":true} · 404 no tenant carries that key · 403 the caller may not ask
CDMS → CIASGET /cias/lookup/users/{id}/attributes?tenantKey=kunde-aon every request that resolves a tenant200 {"attributes":{"regionen":["nord"]}}, empty as well · 403 the caller may not ask
CIAS → CDMSGET /cias/fetchwhen CIAS starts and when an administrator triggers the reconciliation200 with roles and attributes · 403 without a reader role

The first two calls sit on the request path. That is why their timeouts are short (2 seconds by default, connection and answer separately) and deliberately not a tuning knob: a slow CIAS service would otherwise be a slow platform. Better to fail than to wait, because “failed” has a defined answer.

The third call does not sit on the request path. It runs at startup and on demand, may therefore take longer, and remembers nothing.

With which token

The CDMS service asks as itself, never on behalf of the signed-in user.

The token for the lookups
  1. 1
    CDMS
    fetches its own service token from Keycloak (codamai.cias.tenancy.client.credentials)
    With the client ID and secret of its own client, by client credentials. The token is kept for three quarters of its lifetime and then fetched again. It is never logged.
  2. 2
    CDMS→CIAS
    sends it as Authorization: Bearer … to both lookup endpoints
  3. 3
    CIAS
    checks the caller's role – a separate role per endpoint, with no default
    Neither the platform administrator role nor the same role for both questions. One endpoint says whether a key belongs to a served customer, the other hands out the attribute values a row filter sifts by.
  4. 4
    CIAS
    role missing → 403, the CDMS service refuses the request
  5. 5
    CIAS
    role matches → answer
    Result: The CDMS service remembers the answer for the configured time (30 s by default)

Why not simply pass the user’s token along? Two reasons: the signed-in person has no reason to hold a tenant lookup role, and there are requests with no user token at all — a timer, a readiness probe — which would then have nothing to send.

The settings for it:

codamai:
  cias:
    tenancy:
      client:
        credentials:
          token-uri: https://iam.example.com/realms/codamai/protocol/openid-connect/token
          client-id: cdms-node
          client-secret: ${CIAS_LOOKUP_CLIENT_SECRET}
  • A client with a part missing stops the startup.
  • Without a client the service uses a fixed token from codamai.cias.tenancy.client.token. It does not refresh.
  • What happens when Keycloak does not answer or rejects the client is described under When CIAS cannot be reached.

More on service accounts under Sign in as a service (client credentials).

The reverse direction follows the same pattern: CIAS reads GET /cias/fetch with a reader token of its own, which it fetches from Keycloak the same way. Which realm role is enough there is stated by the CDMS side (codamai.cdms.cias.reader-roles, preset to declaration-reader). The answer is the complete permission map of the application, so nothing that should be public.

How CIAS fetches the declaration

Standalone, the CIAS configuration names a URL instead of a bean:

codamai:
  cias:
    authorization:
      declarations:
        reader-token: ${CIAS_DECLARATION_TOKEN}
        modules:
          - name: cias
            client: cias-backend
            bean: ciasIdentityRegistry
          - name: cdms
            client: cdms-backend
            url: https://cdms.internal/cias/fetch

Both forms occur side by side: CIAS is a module itself and reads its own declaration as a bean, while CDMS is a service elsewhere. Every entry names either bean or url, and every one names the Keycloak client its roles land on. Standalone, each service has its own client.

Only 200 counts as an answer. A 404 here does not mean “declares nothing” but “the endpoint is not where the configuration says it is” — otherwise a typo in the URL would retire every role of that module at the next reconciliation. What the reconciliation does then is under Modules register their roles and Reconciliation with Keycloak.

One request from front to back

sequenceDiagram
    participant B as BFF
    participant F as Filter chain (in CDMS)
    participant R as cias-tenancy-client
    participant S as CIAS service
    participant C as CDMS
    participant DB as Tenant DB
    B->>F: POST /api/rest/crm/customer/query + token
    F->>F: check token, exchange it, resolve tenant
    F->>R: may "kunde-a" be served?
    R->>S: GET /cias/lookup/tenants/kunde-a (service token)
    S-->>R: 200 served true
    R-->>F: yes (remembered for 30 s)
    F->>R: attribute values of the person in "kunde-a"?
    R->>S: GET /cias/lookup/users/…/attributes
    S-->>R: 200 regionen nord
    R-->>F: values (remembered for 30 s)
    F->>C: RequestContext filled
    C->>DB: SELECT … (only allowed rows)
    DB-->>C: rows
    C-->>B: data + meta

Both calls are skipped while the answer is in the memory. That memory sits in cias-authentication, so in the CDMS service and per node — not in the small client and not at CIAS.

The CIAS service’s own database

The CIAS service has its own database (CIAS_DATABASE_URL). It routes nothing per tenant: every CIAS table is a system table, there is no database per customer.

Who holds what
CDMS service
  • a system database with its own tables
  • one database per tenant, depending on the persistence target
  • a file storage where applicable
CIAS service
  • one database, with no tenant split
  • one migration run per module with its own history table
  • no business data – users, tenants, roles, groups, processes

An installation may point both at the same database — the CIAS tables are exactly the tables that live in the system database when embedded. It is not required.

When CIAS does not answer

Only standalone can CIAS go down on its own. Then the memory decides:

Tenant gate and attribute lookup during an outage
CIAS answers?already remembered?Result for the request
yes–CIAS decides, the answer is remembered
noyesthe last known answer still applies, however old it is
nonorefused – when in doubt, closed

In one sentence: an outage may not evict anyone who was already working, and may not admit anyone who was not. A remembered refusal stays a refusal — “keep running” means keeping the last answer, not assuming a favourable one.

More counts as an outage than you might think:

  • a timeout or a refused connection,
  • a 401 or 403 – those are this service’s own credentials, not a judgement about the customer,
  • an answer without the served field, because a missing field must not be read as “not served”,
  • for the attribute lookup a 404 as well, because a person with no record is answered with 200 and an empty list.

Details are under When CIAS or Keycloak fails and Admit the tenant (tenant gate).

Watch out

Next

Sources in the code and the knowledge base
  • CIAS/cias-tenancy-client – CLAUDE.md, pom.xml, CiasTenancyClientConfiguration, CiasTenancyClientProperties (base-url, token, 2 s), RemoteTenantLookupAdapter, RemoteTenantBoundAttributeAdapter, ClientCredentialsTenantLookupCredentials, CiasTenancyClientCredentialsProperties, StaticTenantLookupCredentials, TenantLookupCredentials
  • CIAS/cias-tenancy – TenantLookupController (GET /cias/lookup/tenants/{key}), TenantLookupRoles, CiasTenancyConfiguration (lookup-rest)
  • CIAS/cias-user – AttributeLookupController (GET /cias/lookup/users/{id}/attributes), AttributeLookupRoles
  • CIAS/cias-authentication – TenantGate (failure rule, TTL 30 s), AttributeLookup, TenantGateProperties
  • CIAS/cias-authorization – RemoteModuleDeclarationAdapter, DeclarationReaderCredentials; CIAS/cias-spring-boot-starter – CiasDeclarationAutoConfiguration
  • CDMS/cdms-authorization – CiasApi (GET /cias/fetch), CiasReaderRoles
  • CIAS/cias-runtime – pom.xml, application.yml (CIAS_DATABASE_URL, lookup local, lookup-rest, declarations.modules)
  • CDMS/cdms-scaffold – cdms-version-registry.yaml (ciasDependencies REMOTE), CdmsScaffoldService, CdmsReadmeWriter
  • CIAS/cias-kernel/docs/adr/adr-022-tenant-lookup-port.md; CIAS/CLAUDE.md §6, §38
Search