CodamAIDocs
Topicdone

Time-limited roles

Grants with a start and an end: SCHEDULED, ACTIVE, EXPIRED, REVOKED, and how a timer adds and removes them in Keycloak.

Variants
SCHEDULEDACTIVEEXPIREDREVOKED

What this is about

Some permissions only apply for a while: for the length of a project, while someone covers for a colleague on vacation, from the first working day. Keycloak does not know anything like this. There, a person has a role or not. That is why CIAS keeps the validity window of a grant itself and brings Keycloak up to date when the time has come.

A grant can have a start (validFrom) and an end (validUntil), both as a point in time with a time of day, both optional. Without either, it is an ordinary grant without a time limit, and that is the normal case.

The four states

stateDiagram-v2
    direction LR
    [*] --> SCHEDULED: granted, start in the future
    [*] --> ACTIVE: granted, no start or start reached
    SCHEDULED --> ACTIVE: start reached (timer)
    ACTIVE --> EXPIRED: end passed (timer)
    SCHEDULED --> EXPIRED: end passed before it started
    SCHEDULED --> REVOKED: revoked
    ACTIVE --> REVOKED: revoked
    EXPIRED --> [*]
    REVOKED --> [*]
StateMeaningEntered in Keycloak
SCHEDULEDstored, start is in the futureno
ACTIVEappliesyes
EXPIREDend passed, finalno
REVOKEDrevoked earlier, finalno

EXPIRED and REVOKED are different states, even though the person no longer has the role in both cases. An audit must be able to answer why someone lost a permission: because the time ran out or because someone revoked it. That is why an expired grant can no longer be revoked afterwards: 409 cias.authorization.invalid-state.

The timeline

gantt
    dateFormat YYYY-MM-DD
    axisFormat %d.%m.
    section Ben covering
    granted, SCHEDULED        :done, 2026-09-22, 2026-10-01
    ACTIVE, role in Keycloak  :active, 2026-10-01, 2026-10-15
    EXPIRED                   :crit, 2026-10-15, 2026-10-20
Request
POST /cias/admin/role-assignments
{
  "userId": "8c1d…",
  "roleClient": "cdms-backend",
  "roleKey": "invoice-approve",
  "tenantKey": "nordbau",
  "validFrom": "2026-10-01T06:00:00Z",
  "validUntil": "2026-10-15T18:00:00Z",
  "reason": "vacation cover for Anna"
}
Response
HTTP 200
{ "id": "d207…", "state": "SCHEDULED", … }

Both limits are inclusive: exactly at the point in time validUntil, the grant still applies, after that it does not. CIAS rejects an end before the start: 400 cias.authorization.invalid-request. An end that has already passed as well: 422 cias.authorization.validity-window-ended. Nothing is stored then, and nothing reaches Keycloak.

Granting again: changing the window

Per person, role and tenant there is at most one running grant (SCHEDULED or ACTIVE). Granting the same role again therefore does not create a second one but changes the existing one:

Granting the same role again
Window and reasonState beforeWhat happens
the sameanynothing; CIAS returns the existing grant, without a new event
differentACTIVEthe grant takes over window and reason; event WindowChanged
start now in the futureACTIVEfirst removed from Keycloak, then SCHEDULED; the timer puts it back in at the start
start now reachedSCHEDULEDACTIVE and entered in Keycloak, provided the account is in service; otherwise it stays scheduled
end already passedany422 cias.authorization.validity-window-ended, the grant stays as it was

This is how you extend a stand-in: grant the same role again with the new validUntil. grantedBy stays the original grant; who changed the window is in the event.

The timer

A timer is a job that runs at fixed intervals. Here it looks for all grants whose state no longer matches the clock and puts them in order.

What the timer does for each grant

When: SCHEDULED, and validFrom has passed

  1. 1
    CIAS
    sets the grant to ACTIVE
  2. 2
    CIAS→Keycloak
    enters the role

Result: Event Activated. First store, then Keycloak: if Keycloak fails, this tends to refuse, and the next run catches up.

When: ACTIVE or SCHEDULED, and validUntil has passed

  1. 1
    CIAS→Keycloak
    removes the role, if it was entered
  2. 2
    CIAS
    sets the grant to EXPIRED

Result: Event Expired. First Keycloak, then store: the person loses the role even if something fails afterwards.

When: Keycloak cannot be reached for this one grant.

The timer notes it and continues with the next one. On the next run it tries again.

Result: The log contains a warning with the grants that were not finished. Each of them is a permission that someone still has and should no longer have.

SettingDefaultMeaning
codamai.cias.authorization.synchronization.enabledoff in the starter, on in the standalone CIASwhether the timer runs
codamai.cias.authorization.synchronization.intervalPT5Mpause between two runs

If several instances run, the timer may run on all of them. If two hit the same grant, one loses the lock on the record, and the next run does the rest. The calls to Keycloak can be repeated safely. If you want exactly one run, turn the timer off and call the reconciliation from your own schedule.

When it reaches the token

Between the point in time in the record and what an application sees, there are two delays:

  1. up to one interval of the timer, 5 minutes by default
  2. until the person’s token is renewed: the old token still carries the old state

So a grant that ends at 6:00 PM can still take effect for a few minutes after 6:00 PM. See Why revoking permissions takes effect with a delay.

Pitfalls

Next

Sources in the code and the knowledge base
  • CIAS/cias-authorization – RoleAssignment (grant, requireOpenWindow, rewindow, activate, expire, revoke, dueStateAt, isInForceAt), ValidityWindowEndedException, AssignmentState (SCHEDULED, ACTIVE, EXPIRED, REVOKED)
  • CIAS/cias-authorization – RoleAssignmentService (grant, rewindow, synchronizeDueAssignments, applyActivation, applyExpiry), AuthorizationEvent (Activated, Expired, WindowChanged)
  • CIAS/cias-spring-boot-starter – RoleSynchronizationScheduler, CiasSchedulingAutoConfiguration (codamai.cias.authorization.synchronization.enabled, interval PT5M)
  • CIAS/cias-authorization – V1__cias_authorization.sql (ix_cias_role_assignment_due, version)
  • CIAS/cias-authorization/docs/adr – ADR-018
Search