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:
cias-authentication- 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- 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
| Direction | Call | When | Answer |
|---|---|---|---|
| CDMS → CIAS | GET /cias/lookup/tenants/{key} | on every request with a tenant, when nothing is remembered | 200 {"key":"kunde-a","served":true} · 404 no tenant carries that key · 403 the caller may not ask |
| CDMS → CIAS | GET /cias/lookup/users/{id}/attributes?tenantKey=kunde-a | on every request that resolves a tenant | 200 {"attributes":{"regionen":["nord"]}}, empty as well · 403 the caller may not ask |
| CIAS → CDMS | GET /cias/fetch | when CIAS starts and when an administrator triggers the reconciliation | 200 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.
-
1CDMSfetches 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. -
2CDMS→CIASsends it as
Authorization: Bearer …to both lookup endpoints -
3CIASchecks the caller's role – a separate role per endpoint, with no defaultNeither 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.
-
4CIASrole missing → 403, the CDMS service refuses the request
-
5CIASrole matches → answerResult: 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.
- a system database with its own tables
- one database per tenant, depending on the persistence target
- a file storage where applicable
- 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:
| CIAS answers? | already remembered? | Result for the request |
|---|---|---|
| yes | – | CIAS decides, the answer is remembered |
| no | yes | the last known answer still applies, however old it is |
| no | no | refused – 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
401or403– those are this service’s own credentials, not a judgement about the customer, - an answer without the
servedfield, because a missing field must not be read as “not served”, - for the attribute lookup a
404as well, because a person with no record is answered with200and an empty list.
Details are under When CIAS or Keycloak fails and Admit the tenant (tenant gate).