What this is about
For most actions it is enough to be signed in. Some ask for more: salary data may only be read by someone who signed in with a second factor (for example a code from an app), and a template may only be approved by someone who signed in just now.
A step-up is catching up on exactly this “more”: the person signs in again, Keycloak asks only for the missing step, and the portal repeats the action. The person does not have to sign out and loses nothing of what they are looking at.
Two words first:
- The sign-in level says how strongly someone signed in. There are exactly three, in ascending order:
basic(signed in, by any means),mfa(with a second factor) andphishing-resistant(with a factor bound to the address, for example a passkey). A higher level always satisfies a lower one too. - The maximum age of the sign-in says how long ago the sign-in may have happened at most, in whole seconds.
A requirement is always a level and, if needed, a maximum age. More under The sign-in level.
An example, end to end
The model salary says: reading requires the level mfa. A person with the reader role signed in this morning with a password only and now opens the list of salaries.
sequenceDiagram
participant B as Browser
participant P as Sign-in window
participant BFF as BFF
participant F as Filter chain (CIAS)
participant D as CDMS
participant K as Keycloak
B->>BFF: open the list "Salaries"
BFF->>F: POST /api/rest/hr/salary/query with token
F->>F: reads level basic and sign-in time from the token
F->>D: request with RequestContext
D->>D: role for reading? yes. Level mfa reached? no
D-->>BFF: 401 step-up-required|mfa
BFF-->>B: 401, key unchanged
B->>B: dialog: "This action asks for a sign-in with a second factor."
B->>P: click on "Sign in again" opens the window
P->>K: sign-in with acr_values=mfa
K->>P: asks only for the second factor
K-->>BFF: new tokens with acr=mfa into the session cookie
P-->>B: reports "done" and closes
B->>BFF: the same request again
BFF->>F: POST /api/rest/hr/salary/query with the new token
F->>D: level mfa
D-->>B: 200, the salaries
The same steps in words:
-
1BFF→CDMSsends
POST /api/rest/hr/salary/querywith the person's access token -
2Filter chainreads the sign-in level from the claim
acrand the time of the sign-in fromauth_timeBoth come from the token the BFF sent, not from the exchanged one. Ifacris missing or holds an unknown value,basicapplies. Both end up in the RequestContext. -
3CDMSchecks the role for reading first, then the sign-inThe role is there. The level
basicdoes not satisfymfa. -
4CDMS→BFFanswers 401 with the key
cias.authentication.step-up-required|mfaIt adds the standard headerWWW-Authenticate: Bearer error="insufficient_user_authentication", acr_values="mfa". For a search the whole request answers 401. No rows are silently filtered out. -
5BFF→Browserpasses the 401 on with the key unchanged as
messageKey -
6Frontendrecognizes the key and shows a dialog: "This action asks for a sign-in with a second factor."If the action also asks for a maximum age, it adds something like "It must not be older than 5 min." If several requests fail at the same time, there is only one dialog and one sign-in.
-
7Userclicks "Sign in again"This opens a small sign-in window (popup). A click is needed because browsers block a popup without one. If it is blocked anyway, the dialog says so and asks you to allow popups.
-
8Frontend→Keycloaksigns the person in again in the window, with
acr_values=mfa -
9Keycloakalready knows the session and asks only for the missing step, here the second factorThe new token carries
acr=mfa. The BFF puts it into its session cookie like any token. -
10FrontendThe window reports "done" to the portal and closesIt reports over a channel inside the browser (
BroadcastChannelcodamai.step-up) that only works between pages of the same address. -
11BFF→CDMSthe same request again, now with the new token
-
12CDMS→Browserdelivers the listResult: The person sees the salaries. The request was repeated exactly once.
This applies to every request of a portal, not just to this example. The portals handle a step-up in one single place that all their requests pass through. No individual page has to know about it.
The variants
When: The action asks for mfa or phishing-resistant, the person signed in more weakly.
The flow from the example. The portal sends the level as acr_values. Keycloak asks only for the step that is missing for the level. Someone who is already signed in at the level is not asked at all: the window opens and closes right away.
Result: The repeated request succeeds.
When: The action asks for a maximum age, for example 300 seconds, and the sign-in was longer ago.
-
1CDMSanswers 401 with
cias.authentication.step-up-required|basic|300 -
2Frontendshows "This action asks for a sign-in. It must not be older than 5 min."
-
3Frontend→Keycloaksigns in again, with
acr_values=basicandprompt=loginprompt=loginforces Keycloak to really sign in anew, even if the session is still valid. So the new sign-in is fresh in any case, andauth_timein the new token moves forward. -
4BFF→CDMSrepeats the requestResult: The sign-in is younger than 300 seconds, the request succeeds.
When: The action asks for a level for which the sign-in flow in Keycloak has no step.
-
1Frontend→Keycloaksigns in again, with
acr_values=mfa -
2Keycloakcannot produce the level and silently ignores the requestThe new token carries the level the flow reaches, here
basic. -
3CDMSrefuses the repeated request again with 401
-
4Frontenddoes not ask again, but says: "The new sign-in did not reach that. The action was not carried out."Result: No loop. The refusal stays until someone extends the sign-in flow.
When: The person lacks the role for the action.
The module always checks the permission first and only then the sign-in. Without the role there is the usual 403, and nothing is asked about the sign-in. A stronger sign-in would not let the person in anyway.
Result: 403, no dialog
When: Another program calls with the token of its own service account (client credentials).
A service account cannot sign in again. Its token has the level basic and no sign-in time. In CDMS and in the administration of CIAS it is therefore exempt from every requirement. It is protected by its credentials and roles. This does not apply to approvals in CRMS: an approval is a person's signature, and a service account is not supposed to act there at all.
Result: CDMS and CIAS administration: the request goes through. CRMS approval: refusal.
When: The portal already loads the data of a page on the server on the first call.
On the server nobody can be asked. The refusal arrives in the browser as an error together with the page. As soon as the page is set up there, the portal opens the same dialog. After the new sign-in it loads the data of the page again.
Result: The page then shows the data.
If the person cancels the dialog, the refusal stands. The action is then not carried out.
Repeating a write request is safe. The sign-in check runs before anything is stored. One special case is the answer: after a create or update, CDMS returns the stored object, and that is a read. If the model asks for a level for reading, it applies here too. If CDMS refuses at this point, the change is rolled back with the refusal.
What decides the result
| Permission present? | Service account? | Level enough? | Maximum age kept? | What happens |
|---|---|---|---|---|
| no | – | – | – | 403, nothing is asked about the sign-in |
| yes | yes | – | – | goes through (except for an approval in CRMS) |
| yes | no | no | – | 401 step-up-required|<level>, the portal asks |
| yes | no | yes | no | 401 step-up-required|<level>|<seconds>, the portal asks |
| yes | no | yes | yes | The action runs |
“Maximum age kept?” only matters if the action asks for one. Without a maximum age the level is enough. The limit itself still counts as kept.
Who decides what
| Question | Responsible |
|---|---|
| Does this action ask for a stronger or more recent sign-in? | the module, by its own rules: CDMS per model and operation, CRMS per approval stage and kind of document, CIAS for its administration |
| How strongly and when did the person sign in? | the CIAS filter chain, from acr and auth_time of the token that was sent |
| What does the refusal look like? | CIAS defines it: 401, a key and the standard header. Every module answers the same way |
| Which steps does the sign-in ask for? | Keycloak, following the sign-in flow the operator set up |
| When is the person asked, and how often is the request repeated? | the portal: one dialog, one new sign-in, exactly one repetition |
CIAS does not sign anyone in itself and does not ask for any factor. It only gives the levels their names and makes sure that Keycloak writes exactly these names into acr.
Where the requirement is set
A requirement is set when the system is assembled and is fixed after that. In the running system there is no way to change it, neither through an interface nor through a user interface. Whoever could lower a requirement could switch the step-up off.
| Module | What can ask for a sign-in | Where it is set | More |
|---|---|---|---|
| CDMS | every model, per operation (create, read, update, delete). A sub-model inherits the requirement and can only tighten it | in the model, while modeling in the hub | Sign-in level per model |
| CRMS | every approval stage of a template, plus final documents per template key, reading and changing separately | when the system is assembled in the hub, in the CRMS portal: approval stages in the area “Approval”, documents in the area “Sign-in”. The build writes them into the settings of the system; the document defaults can be overridden by an environment variable | Approval of templates |
| CIAS | its own administration in five areas (users, groups, roles, tenants, registrations), reading and changing separately | when the system is assembled in the hub, in the CIAS portal in the area “Administration”. The build writes them into the settings of the system, which an environment variable can override | Sign-in level for the administration |
Without a setting, nothing asks for more than basic and no maximum age. Every signed-in person satisfies that.
Every module declares to CIAS the levels it requires, just like its roles. At reconciliation CIAS compares them with the levels the sign-in flow in Keycloak can produce. See Modules declare their roles.