What this is about
Granting a role means: a person gets a permission, usually in a specific tenant. For this, CIAS stores a grant and enters the role in Keycloak. With the next token, the person has the role.
Before that happens, CIAS checks whether the caller is allowed to do this at all.
The call
POST /cias/admin/role-assignments
Authorization: Bearer <token of Anna, tenant-admin in nordbau>
{
"userId": "5c9e…",
"roleClient": "cias-backend",
"roleKey": "tenant-user",
"tenantKey": "nordbau",
"validFrom": null,
"validUntil": null,
"reason": "new colleague in purchasing"
}HTTP 200
{
"id": "a41f…",
"userId": "5c9e…",
"roleClient": "cias-backend",
"roleKey": "tenant-user",
"scope": "TENANT",
"tenantKey": "nordbau",
"validFrom": null,
"validUntil": null,
"state": "ACTIVE",
"grantedBy": "nordbau",
"reason": "new colleague in purchasing"
}| Field | Meaning |
|---|---|
userId | the ID of the user record in CIAS, not the Keycloak sub |
roleClient, roleKey | the role, as in the catalog. Empty client = realm role |
tenantKey | required for a tenant role, forbidden for a platform role |
validFrom, validUntil | optional, see Time-limited roles |
reason | why, for the audit |
The checks in order
-
CIASRoleDoes the role exist in the catalog, and is it not retired?↳ no 404
cias.authorization.role-not-foundor 409cias.authorization.role-deprecated -
CIASSigned inDoes the request have a valid token?↳ no 403
-
CIASPlatform administrator?yes → all further permission checks are skipped
-
CIASTenant roleDoes the role have the scope
TENANT? Platform roles are never delegated↳ no 403 -
CIASDelegationIs one of the caller's roles in
assignableBy?↳ no 403 -
CIASOwn tenantIs
tenantKeythe tenant from the caller's token?↳ no 403 -
CIASCeilingDoes the caller hold the role themselves, in this tenant or globally?↳ no 403
- Grant allowed
After that, CIAS also checks the receiving person and the tenant:
- A client role with scope
PLATFORMfor a person whose home tenant has an organization: 403. The role would be in the token, but no request would read it, see Realm role, client role, organization role. - A tenant role in a tenant without an organization, that is, a static tenant: 403. In Keycloak, a tenant role is attached to the organization. CIAS never falls back to a global grant, because that would turn a tenant role into a platform role by accident.
All refusals of the permission check look the same: 403 cias.authorization.denied with the text not permitted. CIAS writes the exact reason to the log.
The decision table
| Caller | Scope of the role | delegated to one of the caller's roles | Tenant of the grant | Caller holds the role there | Result |
|---|---|---|---|---|---|
| Platform administrator | – | – | – | – | allowed |
| someone else | PLATFORM | – | – | – | 403 |
| someone else | TENANT | no | – | – | 403 |
| someone else | TENANT | yes | a different one than their own | – | 403 |
| someone else | TENANT | yes | their own | no | 403, the ceiling applies |
| someone else | TENANT | yes | their own | yes | allowed |
The own tenant comes from the caller’s token, never from the request. Otherwise every tenant administrator could grant a delegated role in any other tenant. How the ceiling is counted is described in The ceiling: nobody grants more than they have.
The flow when everything is right
sequenceDiagram
participant A as Anna (tenant-admin)
participant C as CIAS
participant DB as CIAS database
participant K as Keycloak
A->>C: POST /cias/admin/role-assignments
C->>C: check role, delegation, tenant, ceiling
C->>DB: is there already a running grant? otherwise store (ACTIVE)
C->>K: person into group cias-backend:tenant-user of organization nordbau
C-->>A: 200 with the grant, event Granted
First store the grant, then Keycloak. If Keycloak fails, CIAS records more than the token carries. That tends to refuse rather than allow too much, and a second call catches up.
The call can be repeated safely: if there is already a running grant for this person, this role and this tenant, CIAS does not create a second one. Two running grants of one role would have two end dates, and nobody would know which one applies. With the same window and reason CIAS returns the existing one; with a different one the existing grant takes it over, see Time-limited roles.
How the role is entered in Keycloak depends on level and scope, see Realm role, client role, organization role.
Variants
When: The token carries a role that the installation counts as platform administrator, in the standalone CIAS platform-admin.
Delegation, own tenant and ceiling are skipped. A platform administrator holds every permission, so they meet the ceiling instead of being exempt from it.
Result: Any role that is not retired, in any tenant or globally.
When: Anna has tenant-admin in nordbau and grants tenant-user in nordbau.
In the standalone CIAS, tenant-user is delegated to tenant-admin. Anna holds tenant-user in nordbau too.
Result: allowed
When: Anna has tenant-owner and tenant-admin in nordbau and grants tenant-admin to Ben.
tenant-admin is delegated to tenant-owner, and Anna holds tenant-admin herself.
Result: allowed
When: Ben has tenant-admin but not tenant-owner and wants to pass on tenant-admin. Or somebody in the tenant wants to grant tenant-owner.
tenant-admin is only delegated to tenant-owner and platform-admin, tenant-owner only to platform-admin.
Result: 403
When: A role is delegated to tenant-admin, but Anna does not hold it.
Delegation says who may pass a role on, the ceiling says how much. Both have to agree.
Result: 403
When: Anna grants tenant-user with tenantKey: suedlogistik.
Anna's token names nordbau.
Result: 403
When: A tenant role is to be granted in the static tenant stadtwerke-nord.
Without an organization, there is no place for a tenant role in Keycloak.
Result: 403, even for a platform administrator
Viewing grants
| Call | Returns |
|---|---|
GET /cias/admin/role-assignments?userId=… | all grants of a person, including ended ones |
GET /cias/admin/tenants/{tenantKey}/role-assignments | all grants in a tenant |
GET /cias/admin/roles/assignable | the roles you may grant in principle, without retired ones |