What this is about
Keycloak only knows the name of a role. It does not know what the role is for, who may pass it on, and whether it applies to the whole platform or only in one tenant. CIAS keeps this knowledge in the role catalog: one row per role, with everything CIAS needs to know about it.
A catalog entry
GET /cias/admin/roles/tenant-user?client=cias-backend{
"client": "cias-backend", ← client in Keycloak, empty = realm
"key": "tenant-user", ← name, exactly as in Keycloak
"module": "cias", ← which module registered it
"scope": "TENANT", ← applies in one tenant
"displayName": "Tenant member", ← what people read
"description": null, ← explanation for administration
"group": "tenant", ← display group in the user interface
"assignableBy": ["tenant-admin", "platform-admin"],
"deprecatedAt": null ← set = retired
}| Field | Meaning | Can be changed |
|---|---|---|
client + key | the identity of the role, the same two values as in Keycloak | no |
module | the module that registered the role. Empty for roles that nobody registered | no |
scope | PLATFORM or TENANT, see below | no |
displayName, description | what people read | yes |
group | under which heading a user interface shows the role | yes |
assignableBy | who may grant the role, see below | yes |
deprecatedAt | since when the module no longer registers the role | only by the reconciliation |
The identity: client and key
A role is the pair of client and key, for example cdms-backend / model-editor. In Keycloak, a client is an application that requests or checks tokens. The key alone is not enough: two modules may both have a role model-editor, and those are two different permissions.
If the client is missing, it is a realm role: a role that applies to the whole platform in Keycloak. There are only a few of these, see The platform’s realm roles. What the levels mean in the token is described in Realm role, client role, organization role.
The scope: platform or tenant
The scope says how far a grant reaches. It is a security boundary and is set when the role is created.
| PLATFORM | TENANT | |
|---|---|---|
| Reach | everywhere, follows the person into every tenant | only in one tenant |
| Grant names a tenant | no, forbidden | yes, required |
| Who grants | only a platform administrator | platform administrator, or whoever got the role delegated |
| How it is granted in Keycloak | directly on the account | through the tenant's organization |
| Examples | platform-admin, user | tenant-admin, all roles that a module registers |
With TENANT, the same person can be an administrator in one tenant and only a reader in another.
The scope cannot be changed. If a tenant role became a platform role, every grant that already exists would suddenly apply everywhere, without anything being visible on a single grant. An attempt ends with 400 cias.authorization.invalid-request.
Every role that a module registers gets the scope TENANT. Platform roles only come from the installation’s configuration. The reason is described in Modules register their roles.
The delegation: who may grant
assignableBy names the roles whose holders may grant this role. In the example above, everyone with tenant-admin may grant the role tenant-user.
- Empty means: only a platform administrator. An empty list is a lock, not a free pass.
- The list only names keys, no clients. A caller’s roles arrive in the token as a flat list of names. A client could not be checked there.
- A platform role is never delegated, no matter what is in the list.
Delegation alone is not enough to grant. Whoever grants must also hold the role themselves. The full check is described in Grant a role.
Retired instead of deleted
If a module no longer registers a role, CIAS does not delete it but retires it: deprecatedAt gets a date and time.
| active role | retired role | |
|---|---|---|
| existing grants | apply | still apply, unchanged |
| new grant | possible | 409 cias.authorization.role-deprecated, also for platform administrators |
| revoke | possible | possible |
| in the “assignable” list | yes | no |
| role in Keycloak | exists | stays |
The date does not change on later reconciliations. It says when the role was withdrawn, not when CIAS last started. If the module registers the role again, it is active again, and the grants were never gone. More in Reconciliation with Keycloak.
The endpoints
| Call | Who | Effect |
|---|---|---|
GET /cias/admin/roles (?scope=…) | any signed-in person | all roles |
GET /cias/admin/roles/page?query=…&scope=…&page=…&size=… | any signed-in person | one page, with the total count |
GET /cias/admin/roles/assignable | any signed-in person | the roles that you may grant in principle |
GET /cias/admin/roles/{key}?client=… | any signed-in person | one entry, otherwise 404 |
POST /cias/admin/roles | platform administrator | create an entry, 409 if it exists |
PUT /cias/admin/roles/{key} | platform administrator | change description, group, delegation |
DELETE /cias/admin/roles/{key}?client=… | platform administrator | remove an entry |
Creating an entry by hand does not create a role in Keycloak. A typo in the catalog should not create a permanent role in Keycloak. Roles of modules are created in Keycloak through the reconciliation. A role created by hand belongs to no module, so no module ever retires it either.
What the catalog does not know on purpose
- Composite roles. Keycloak can build a role from other roles. That stays in Keycloak, because Keycloak decides anyway what ends up in the token. A second composition in CIAS would be a second answer, and it would lose.
- Single permissions. A role does not carry a list of what it may do. Each module decides that itself, in CDMS for example per model and operation.