What this is about
A module registers its attributes in the same declaration as its roles, see Modules register their roles. A declaration is the list with which a module says: “This is what I need.” CIAS reads it during the reconciliation and turns it into three things:
- an entry in Keycloak’s user profile, so that an account can store the value at all,
- a claim mapper on the module’s client, so that the value arrives in the token,
- an entry in CIAS’s attribute catalog, which records who registered the attribute and who may write it.
What a registration contains
Each attribute has six parts:
| Part | Meaning | if missing … |
|---|---|---|
key | the name, exactly like this in the profile and in the token | required |
multivalued | a list instead of one value. An attribute filter almost always needs a list | one value |
defaultValue | what accounts get that are older than the attribute | no default value |
required | the module cannot work without the value | optional |
selfEditable | the person may change the value themselves | administrators only |
binding | USER: one value per person. USER_IN_TENANT: one value per person and tenant | USER |
In Java a module writes this with short building blocks:
Attribute.optional("region").asMultivalued() // optional, list
Attribute.required("customerId", "unknown") // required, with default value
Attribute.preference("locale", "de") // the person changes it themselves
Attribute.optional("areas").asMultivalued().perTenant() // one list per tenant
A module running separately delivers the same list as JSON under GET /cias/fetch:
GET /cias/fetch{
"roles": [ … ],
"attributes": [
{ "key": "region", "multivalued": true, "defaultValue": null,
"required": false, "selfEditable": false, "binding": "USER" }
]
}The binding field arrives exactly as in embedded operation. If it is missing, USER applies, as for a module older than the field. CIAS does not guess a value it does not know: the registration of that module then counts like an unreachable module, and the reconciliation leaves everything as it was.
A registration cannot contain format rules such as a length or a pattern. Whether a new rule is safe depends on the values already stored in the accounts, and the module does not know them. Whoever runs the realm maintains such rules by hand in the user profile. CIAS leaves them there.
The variants
When: required is false. This is how CDMS registers every attribute from the model.
The account may have no value. What the module does then is up to the module. An attribute filter in CDMS refuses the request when the value is missing.
Result: accepted
When: required is true, defaultValue is set.
Keycloak fills in the default value for all accounts that do not have it yet. The value is required of a person filling in a form, never of CIAS writing through the admin API.
Result: accepted
When: required is true, defaultValue is missing or empty.
All accounts older than the attribute would have no value. Keycloak then refuses every change to such an account, including suspending it. Taking the permission away would be the first thing to break.
Result: rejected: the whole declaration of this module, including its roles, status REJECTED
When: selfEditable is true, as for locale.
The person may change the value themselves, also on Keycloak's own account page. Administrators still may too.
Result: accepted
When: binding is USER_IN_TENANT.
The person can have a different value in each tenant. CIAS keeps the values, not Keycloak. See One value per person or per tenant.
Result: accepted
When CIAS rejects a registration
| Finding | affects | Consequence |
|---|---|---|
| required attribute without default value | one module | REJECTED: the whole declaration of the module drops out of this run |
| the same attribute twice, differently, in the same module | one module | REJECTED |
| the catalog keeps the attribute for another module, and this module describes it differently | one module | REJECTED |
| two modules describe the same attribute differently in the same run | everything | SKIPPED: the run writes nothing, for no module |
| two modules describe the same attribute the same way | – | no contradiction, both may register it |
Why so strict? The user profile is one document for the whole realm. Two different descriptions of the same attribute describe the same field, and whoever wrote last would win. Nobody would notice until a filter returns wrong rows.
What comes about in Keycloak
-
1CIAS→Keycloakadds
regionto the user profile: multivalued, administrator and person may view it, only administrators may change it, no default value, not required -
2CIAS→Keycloakcreates a claim mapper
regionon the module's client: value of the attribute → claimregionin access token, ID token and UserInfo, as a list -
3CIAScreates the entry
regionin the catalog, ownercdmsResult: From now on an account can carryregion, and the client's token brings it along
CIAS writes the profile once per run for all modules together, because Keycloak replaces it entirely on every write. Whatever CIAS did not register stays in it, including rules and display names someone maintained by hand. If a module changes its registration, for example from one value to a list, the next reconciliation adjusts profile and mapper.
The attribute catalog
Every logged-in person can read the catalog:
GET /cias/admin/attributes/regionHTTP 200
{
"key": "region",
"module": "cdms",
"displayName": "region",
"description": null,
"group": null,
"multivalued": true,
"required": false,
"defaultValue": null,
"selfEditable": false,
"binding": "USER",
"assignableBy": [],
"deprecatedAt": null
}| Field | Meaning |
|---|---|
module | who registered the attribute. As long as it is registered, no other module can describe it differently |
displayName, description, group | for admin screens. The registration does not name them; so the name is the key |
assignableBy | which roles may write the value per tenant. Empty means: platform administrators only. See Who may write an attribute |
deprecatedAt | set when no module registers the attribute any more. See How an attribute goes away again |
The catalog is read-only. Entries are created by the reconciliation alone: an attribute exists because a module needs it. GET /cias/admin/attributes returns all entries; without a login you get 403, for an unknown key 404.