What this is about
Every person has an account in Keycloak: address, password, MFA, sessions. In addition, CIAS keeps its own user record. Why two?
- Keycloak is responsible for sign-in. What a person is in business terms, which tenant they belong to, and whether they are suspended belongs to CIAS.
- If everything were only in Keycloak, switching the IdP would mean a data migration of all users.
- Without its own record, there would be nothing after a registration that CIAS could suspend, list, or assign to a tenant.
Record and account
flowchart LR
subgraph C["CIAS: cias_user"]
direction TB
c1["id (own UUID)"]
c2["externalUserId"]
c3["email, displayName"]
c4["status"]
c5["homeTenantKey"]
c6["attributes"]
end
subgraph K["Keycloak: account"]
direction TB
k1["id (sub in the token)"]
k2["username = email"]
k3["password, MFA, sessions"]
k4["enabled"]
k5["profile attributes"]
end
c2 -- "points to" --> k1
c4 -. "ACTIVE ↔ enabled" .-> k4
The fields
| Field | Meaning |
|---|---|
id | own ID in CIAS, a UUID. All other CIAS data points to it |
externalUserId | the ID of the account in Keycloak, sub in the token. Unique |
email | the address and also the login. Always stored in lowercase, unique, cannot be changed |
displayName | display name. If it is missing, the address is shown |
status | PENDING, ACTIVE, SUSPENDED, or CLOSED, see The lifecycle of a user |
homeTenantKey | the home tenant, or empty in an installation without tenants |
attributes | business notes as key-value pairs, see Maintain a person’s attributes |
The record lives in the system database, never in a tenant’s database.
The home tenant is one tenant. CIAS does not keep other tenants the person is a member of on the record. They are stored in Keycloak and arrive via the token.
What it never contains
No password, no password hash, no MFA secret, no recovery code, no token. An automated architecture test even prevents anyone from adding a field with such a name. This is why you can safely back up, copy, and read the record in support.
How a record is created
When: A registration is completed.
At the end of provisioning, a hook creates the record: address, name from first and last name, tenant from the registration, the other form fields as attributes. Then it sets the record to ACTIVE. If a record already exists for the account, it stays as it is.
Result: See What happens on completion.
When: Keycloak has accounts that CIAS does not know yet.
A platform administrator starts the import. CIAS creates a record for every unknown account.
Result: See Import existing accounts.
There is no endpoint to “create a user by hand”. New people come in through registration.
Queries for administrators
All endpoints are under /cias/admin/users and are for platform administrators only.
| Call | Result |
|---|---|
GET /cias/admin/users/page?query=&page=0&size=50 | page with items, page, size, total, hasMore. query searches address and display name, case-insensitive |
GET /cias/admin/users?tenantKey=nordbau | all people with this home tenant, sorted by address. Without tenantKey: all people without a tenant |
GET /cias/admin/users/by-email?email=… | one person or 404 |
GET /cias/admin/users/{id} | one person |
GET /cias/admin/users/3f0c…{
"id": "3f0c…",
"externalUserId": "7f3c…",
"email": "anna@nordbau.example",
"displayName": "Anna Berg",
"status": "ACTIVE",
"homeTenantKey": "nordbau",
"attributes": { "department": "Einkauf" }
}Errors come as { "error": "cias.user.…", "message": "…" }. If the role is missing, CIAS responds with 403 cias.user.administration-denied, no matter whether the person exists. An unknown ID results in 404 cias.user.not-found.