What this is about
The Keycloak adapter (cias-iam-keycloak) is the only piece of CIAS that knows Keycloak. It implements the ports by calling Keycloak’s Admin REST API. That is the HTTP interface through which a realm can also be managed from outside: create accounts, grant roles, maintain groups.
For that, CIAS logs in to Keycloak itself, with its own service account. That is a client without a login page that only has administration rights. In the shipped realm it is called cias-admin.
What the adapter touches in Keycloak
flowchart TB
subgraph R["Realm (e.g. codamai)"]
U["Accounts<br/>user name = email"]
O["Organizations<br/>alias = tenant key"]
OG["Groups in an organization<br/>one per granted role"]
G["Groups in the realm<br/>marker cias-managed"]
RR["Realm roles"]
CR["Client roles<br/>per client"]
UP["User profile<br/>which attributes exist"]
PM["Protocol mappers<br/>attribute → claim, per client"]
end
O --> OG
OG -. "carries" .-> RR
OG -. "carries" .-> CR
G -. "carries" .-> RR
G -. "carries" .-> CR
U -. "member" .-> O
U -. "member" .-> OG
U -. "member" .-> G
Each box stands for one kind of object in the realm. The dashed lines show what hangs on what. The following sections go through the boxes one by one.
How CIAS logs in
Before the adapter does anything, it gets a token for its service account. It keeps it and gets a new one shortly before it expires (30 seconds before).
When: client-secret is set, username is not.
CIAS logs in with client ID and secret (client credentials). The service account needs administration rights: manage-users, view-users, query-users for accounts, manage-realm, view-realm, query-realms for organizations, and manage-clients, view-clients, query-clients for client roles.
Result: the way for every installation
When: username and password are set.
CIAS logs in as a person (password grant), usually as an administrator in the master realm named by auth-realm. Meant for test realms that disappear right away.
Result: tests and throwaway realms
| Setting | Meaning |
|---|---|
codamai.cias.iam-provider | keycloak switches this adapter on |
codamai.cias.keycloak.server-url | address of Keycloak, without a trailing slash |
codamai.cias.keycloak.realm | the realm CIAS manages |
codamai.cias.keycloak.client-id | the service account CIAS logs in as |
codamai.cias.keycloak.client-secret | its secret |
codamai.cias.keycloak.username, password | a person instead, only for tests |
codamai.cias.keycloak.auth-realm | where the token comes from; if not set, the same realm |
codamai.cias.keycloak.password-setup-actions | which required actions a password link demands; if not set, only UPDATE_PASSWORD. Only UPDATE_PASSWORD, UPDATE_PROFILE and TERMS_AND_CONDITIONS are permitted, and UPDATE_PASSWORD has to be among them, otherwise CIAS does not start |
If the address, the realm, the client ID, or both the secret and the user name are missing, the application does not start. The standalone CIAS takes the address and the realm from the same values it uses to validate tokens. That way it always manages the Keycloak people log in to.
Accounts
- The user name is the email address. Keycloak requires a user name, so the adapter writes the address into both fields.
- New accounts are disabled. An account is created as “not enabled” and “address not verified”. The address is taken from then on, but nobody can log in yet. See One flow, four variants.
- Lookup by address ignores upper and lower case.
- Every change reads first. When writing, Keycloak replaces the whole account. So the adapter reads it, changes exactly one field and writes it back. Otherwise “enable” would delete the required actions on the side, or “set language” the tenant.
- Disabling only sets “not enabled”. Data, memberships and roles stay. See Suspend, reactivate, close.
- Requiring “set password” adds the required action
UPDATE_PASSWORD, exactly once, even if the call is repeated. - Attributes are always stored as a list in Keycloak. The adapter writes a value as a list with one entry and reads the first value when reading back.
- Deleting only exists for cleanup, for example for accounts whose registration was never confirmed. If the account is already gone, that is not an error.
Organizations and memberships
An organization is how Keycloak represents a dynamic tenant. Its alias is exactly the tenant key. That is how CIAS finds the tenant again from a token.
- Create: the adapter first checks whether the alias is already taken and then reports a conflict. Two organizations with one alias would mix up tenants.
- Lookup goes via the alias (
q=alias:…), not via the name search. The name search would find an organization whose display name happens to contain the key. From the result the adapter takes only the exact match. - Membership is granted and revoked. Granting an existing membership again changes nothing.
- Deleting only exists for cleanup. A tenant that ends keeps its organization.
When CIAS creates an organization is described under Create and provision a tenant.
Global roles
A global role hangs directly on the account and applies in every tenant.
- Realm roles and client roles are granted separately, each through its own path in Keycloak.
- Keycloak gives every account the container role
default-roles-<realm>. The adapter leaves it out when reading. It is not a business role. - Creating roles: client roles are created by the reconciliation, because a module declares them. The adapter creates realm roles only when the installation names them in its configuration, never at the request of a module. A role that already exists stays unchanged, including its description.
- Description: Keycloak stores at most 255 characters. The adapter cuts a longer description and appends
…. In the CIAS role catalog it stays complete.
Roles in an organization: groups
This is the part that surprises most. An organization in Keycloak has no roles. It has groups. A role that only applies in one tenant is therefore a group in the organization that carries this role. The person is a member of that group.
flowchart LR
P["Account<br/>anna@nordbau.de"] -- "member" --> O["Organization<br/>alias nordbau"]
P -- "member" --> G1["Group in nordbau<br/>cdms-backend:model-editor"]
P -- "member" --> G2["Group in nordbau<br/>pruefer"]
G1 -. "carries client role" .-> C["cdms-backend / model-editor"]
G2 -. "carries realm role" .-> RR["pruefer"]
O --- G1
O --- G2
The name of the group depends on the kind of role:
| Kind of role | Name of the group in the organization | Example |
|---|---|---|
| client role | <client>:<role> | cdms-backend:model-editor |
| realm role | <role> | pruefer |
Why the client is in the name: two modules may both have a role model-editor. If the group were only called model-editor, it would carry both roles, and whoever gets one would get the other as well.
When: CIAS grants a role that only applies in this tenant.
-
1CIAS→Keycloaklooks for the group with the matching name in the organization and creates it if it is missing
-
2CIAS→Keycloakattaches the role to the group, through the organization's own path
-
3CIAS→Keycloakadds the person to the groupIf the person is not a member of the organization, Keycloak refuses
-
4Keycloakthe role appears in the
organizationclaim from the next token onResult: role applies in this tenant
When: CIAS takes back a role in a tenant.
The adapter removes the person from the group. The group and its role stay, because other people in the tenant may still be members. The membership in the organization stays as well. If the group does not exist, there is nothing to do.
Result: role gone, person stays in the tenant
Two things in Keycloak have to be right for this:
- Groups in an organization can only be changed through the organization interface. The ordinary path for groups refuses them. So the adapter always uses the path via the organization.
- These roles only get into the token if the client has a mapper of type
oidc-organization-group-membership-mapperwithaddGroupRoleMappings. Without it, the roles are granted correctly, but no token mentions them. The shipped realm has it. See What is read from the token.
In the token these roles are not among the global roles, only in the organization claim. That way an organization can never add to the platform-wide rights of a person.
Groups in the realm
The groups of CIAS are something different from the groups in an organization. They live in the realm, apply platform-wide and carry the marker cias-managed. The adapter only changes, reads and deletes groups with this marker. It never touches groups in organizations this way. How that works in detail is described under Manage groups and members and Reconciliation with Keycloak.
Profile attributes with permissions
The adapter creates an attribute that a module declares in the user profile. That is a single document per realm. The adapter reads it whole, changes only the entries in question, and writes it back whole. Everything else in it stays as it was: the fields Keycloak brings itself (user name, email with their validation rules), and whatever an operator entered by hand.
For every attribute the adapter sets four things:
| Field in the profile | What the adapter writes |
|---|---|
multivalued | whether the attribute is a list |
permissions | who may see it and who may change it, see below |
defaultValue | the value for accounts that are older than the attribute, or nothing |
required | only if a person has to fill it in a form; then roles: [user], never for administration |
When: The attribute describes something the installation decides, for example tenant (the tenant of the person).
view: [admin, user], edit: [admin]. The person sees the value, only administration can change it, and that includes CIAS. If a person could change their own tenant, they could reach another customer's data.
Result: only CIAS and administrators write
When: The attribute belongs to the person, for example their language. The module declares it as self-editable.
view: [admin, user], edit: [admin, user]. The person changes it themselves, also on Keycloak's account pages. Administration may still correct it.
Result: the person and CIAS write
The adapter never removes an attribute from the profile. If a module no longer declares an attribute, it stays, together with all values on the accounts. If an entry differs, for example because an attribute became a list, the adapter corrects exactly the four fields above. See Registering attributes.
Claim mappings
An attribute in the profile is not yet in any token. For that, the adapter creates a protocol mapper on the client: a rule that copies an attribute of the account into a claim of the token.
- Type
oidc-usermodel-attribute-mapper. - Mapper, attribute and claim have the same name. Two names would allow writing into a claim nobody reads.
- The value goes into the ID token, the access token, userinfo and token introspection, as a string or a list of strings.
- If the mapper already exists but with different settings, the adapter corrects it. Unlike the description of a role: here it is about what is in the token.
More under The path into the token.
How answers from Keycloak are translated
| Keycloak answers | when reading | when writing |
|---|---|---|
| 2xx | result | done |
| 404 | empty result | IamNotFoundException |
| 409 | – | IamConflictException |
| 5xx, timeout, no connection | IamUnavailableException | IamUnavailableException |
| other 4xx | IdentityProvisioningException | IdentityProvisioningException |
Where a write may come twice, a specific answer counts as success: a 409 when creating a role that another process has just created, or a 404 when deleting something that is already gone.
The adapter never puts the text of a Keycloak answer into an error message. It can contain addresses or attribute values.
The health check
If the starter sets up the adapter and Actuator is present, the adapter reports under the name iam in the health report whether CIAS can use Keycloak. For that it gets a token for its service account and reads the realm with it.
When: Getting the token and reading the realm succeed.
Status UP, with the name of the realm.
Result: UP
When: Keycloak does not answer, the secret was changed, the service account is switched off, or the realm was renamed.
Status DOWN, with the name of the realm and only the kind of error, never its text. An error text can contain a URL.
Result: DOWN
That is more than “is Keycloak running?”. A Keycloak that runs, but that CIAS can no longer log in to, would otherwise only show up at the next registration.
The check does not count towards the readiness of the application. Without Keycloak, CIAS can still do a lot: validating tokens only needs the public keys, reading tenants only the database. If the application were taken out of service because of Keycloak, a partial outage would become a full one.
What Keycloak has to provide
- Keycloak 26.7 or newer. Only from this version on do organizations have groups with roles.
- The
organizationfeature has to be switched on at the server (KC_FEATURES=organization, singular) andorganizationsEnabledin the realm. - The service account with the rights above.
- The clients whose roles CIAS grants. CIAS does not create a client. An unknown client is an
IamNotFoundException. - The mapper for roles in organizations on every client whose tokens CIAS reads.
The realm under cias-runtime/deploy/keycloak meets all of this and is the template for your own.