Re-authenticate for a sensitive action
Holding the right capability is not always enough. A defined set of the engine’s routes want a fresh proof that you are still at the keyboard before they will act, so a browser left signed in on an unattended machine cannot repoint your archives, grant itself a role, or approve a dangerous change. This is step-up re-authentication, ASVS V7.5.1 and V7.5.3.
You will meet it as a prompt to re-verify part-way through an action you have already started. That is the gate working, not a session that has expired.
What is gated
Where your data goes. Set, add, edit or remove an archive destination, and change which is the default.
Where your telemetry goes. Set or remove the SIEM push destination, send a test event to it, set or remove the OTLP metrics push.
Who can sign in, and with what authority. Add, remove, enable or disable an identity-provider connection; roll over a SAML signing certificate; delete a passkey credential; register a new one; grant or revoke a role, a group-to-role mapping, or a custom role definition; revoke every sign-in factor an address holds.
That last one is the strictly larger sibling of deleting a passkey credential, and it is worth naming separately. Deleting a credential removes one. The offboarding revoke removes every credential for an address, plus their banked recovery codes, plus every invite issued to them that is still live, in a single call. It is the one route in this engine that destroys recovery codes, which cannot be re-derived, so plan an urgent offboarding around the prompt rather than meeting it mid-ceremony. See offboarding a member.
Keys. Install a key, add an operational key, rotate the break-glass key, switch to break-glass-only posture, retire the break-glass bearer token.
Recovery. Regenerate recovery codes.
Restores, retention and attestations. Approve a restore, apply a restore, apply or approve a retention prune, create an attested verification session.
Change control. Approve a pending config change; approve a pending dual-control owner action.
Posture. Record a posture acceptance, and withdraw one. Both directions are gated, because quietly un-accepting a risk changes what the posture score reports just as surely as accepting it did.
Custody. Email a custody share to a recipient.
Detection config. Create, replace or delete a notification rule, and create, replace or delete a notification channel. Both directions are gated, and the reason is availability of evidence rather than confidentiality: a notify emission is redaction-safe, so repointing a channel steals nothing, but silencing an account is the move made before an attack rather than after it. Suppression needs no delete either, since a channel repointed at a sink nobody reads or a rule narrowed until it matches nothing goes quiet with every record still in place, so the set direction is gated as well as the delete.
Credential minting. Mint an owner-minted pull credential, in any of its three scopes. The route carries the same custody weight as granting a role, which is gated in the same way.
Sessions. Every way to end a session requires step-up: signing out a member’s sessions, signing out everyone, signing out your own other sessions, and signing out one of your sessions. The last two reach no session but your own, so a sign-in of any method within the last five minutes satisfies them. The engine gates them because a stale session that survived a compromise could otherwise sign the legitimate operator’s other tabs out unchallenged. The two that reach another member’s session hold the usual passkey rule.
The engine’s step-up gate is STEPUP_SUBS in engine/src/admin/router-core.ts, and a small number of routes call it directly outside that set. Treat the categories above, not a route count, as the source of truth.
Which sign-in methods are exempt, and why
A bare break-glass token is exempt. That token is all-or-nothing by design: anyone holding it already has every power the engine has, so demanding a second proof from it would add nothing while removing the one credential that works when everything else is broken.
The engine judges Cloudflare Access on its own sign-in time. An Access session has no passkey to assert with, so the engine reads the issued-at time of the verified Access assertion instead. A sign-in within the last five minutes passes. The engine refuses an older one, or an assertion with no issued-at time, with 401 { stepUpRequired: true, reauth: "access" }. The console does not open the passkey prompt for that answer: it tells you to sign in to Access again, then you retry (requireStepUp, engine/src/admin/router-core.ts; console/src/lib/errors.ts).
A first-party cookie session is not exempt, and that is the common case. The engine asks its scheduler whether that session was authenticated recently enough, or whether you have presented a valid single-use step-up token. The engine then consumes that token, so the same proof cannot be replayed.
What a 401 means here
On a gated route a 401 carrying stepUpRequired is the opening move, not a failure. The console catches it, runs the re-verification, and retries the original request. Nothing has gone wrong and you have not been signed out; a 401 on these routes is how the conversation starts.
That is worth knowing because it changes how you read your own audit and diagnostic records: a step-up 401 is not an authentication failure to investigate.
If you dismiss the prompt, or no prompt appears
A ceremony that does not finish leaves the original action undone. The console says which way it ended, because one of the endings needs a different remedy from the others: an operator with no passkey on their device cannot approve a prompt that never opens.
There are six endings you can be shown.
“The engine would not start the identity check.” The engine refused to open the ceremony, so no prompt was ever raised. Nothing was changed. This is the ending that is worth reading carefully, because it has two very different causes and the message reads the same for both. If it clears on a retry, it was transient. If it does not clear, and it does not clear for anybody, see the fleet-wide section below: the usual cause is a configuration gap on the engine and no amount of retrying will move it.
“That passkey was not accepted for the identity check.” A prompt opened, you answered it with a passkey, and the engine rejected the answer when it verified the assertion. Nothing was changed. Retry with a passkey you enrolled for this account, since a credential the engine does not hold for you fails at exactly this step.
“The passkey check was not completed.” The prompt was dismissed, or it timed out, or your device holds no passkey enrolled for you. Nothing was changed. Those look identical on purpose and cannot be separated: WebAuthn answers a dismissed prompt, a timed-out prompt and a device with no matching credential with a single indistinguishable error, so that no website can quietly test whether you hold a passkey. If you saw a prompt and dismissed it, retry and approve it. If no prompt appeared at all, this device has no passkey enrolled for you, and retrying will keep producing the same nothing.
“This action needs a passkey assertion and no passkey is enrolled for your account.” You signed in through an identity provider and hold no passkey, so there is nothing to assert with. Nothing was changed. A retry cannot succeed. Enrol a passkey from the sign-in screen first.
“Your browser refused the identity check.” The engine answered, but the browser refused the ceremony before it opened a prompt. Nothing was changed. The message makes no claim about which passkeys the device holds. Try another browser or device.
“The identity check could not be completed because the console did not get a usable answer from the engine.” The request to start or finish the check did not get a usable answer. Nothing changed, and your session is still active. Try again in a moment.
Getting a passkey onto a device that has none
The console’s Access and security tab lists the passkeys you already hold and lets you revoke one. It has no control that enrols a new one, so it is not where this is fixed, and its own empty state says so.
Enrolment happens on the sign-in screen, and there are two ways to reach it:
- With your recovery codes. Sign out, sign in with one of your banked recovery codes, and the console takes you straight to passkey enrolment. A recovery code is both a way in and a way to re-enrol, which is what it is banked for.
- Without them. Ask an owner to re-invite your address. The invite mints a single-use, email-bound registration link, and that link’s enrolment form is the other entry point.
There is no third path. A passkey cannot be enrolled from inside an ordinary signed-in session, so an operator who has neither a recovery code nor an invite needs an owner before they can clear this. See session management for where recovery codes come from and offboarding a member for what destroys them.
The gate fails closed, and what that looks like
If the engine cannot reach the component that answers the freshness question, the action is denied. Failing closed is the right choice for a control whose whole job is to stop an unattended session acting, but it produces a failure mode worth recognising.
If that component is up but broken, every gated action is refused with the same 401 stepUpRequired that a legitimate ceremony opens with. The symptom is “it keeps asking for my passkey and never accepts it”, on every sensitive action, for every user at once.
So the way to tell the two apart is the blast radius. One user, one action, and the prompt clears on a retry: that is the gate working. Every user, every gated action, and the prompt never clears: that is not your device or your passkey, and re-enrolling will not fix it. It is an engine-side fault. The engine records it as a distinct signal rather than as a run of authentication failures, and it belongs in a support bundle.
The other fleet-wide cause, and it is a one-line fix
A second fault produces the same “every gated action is refused for everybody” shape and is not a fault at all in the usual sense. A step-up ceremony has to be bound to an origin, and the engine takes that origin from its CONSOLE_ORIGIN setting. If CONSOLE_ORIGIN is not set, the engine refuses to open any ceremony and answers 501 with passkey_not_configured (engine/src/admin/router-account-session.ts), so every step-up gated action on that deployment fails, for every user, from the moment it is deployed.
Tell it apart from the fail-closed case by two things. It appears immediately on a new or reconfigured deployment rather than starting mid-life. And it travels with the setup error the console shows on ordinary reads, “Engine CONSOLE_ORIGIN is not set to this console”, because the two have the same single cause. If you are seeing that block error anywhere in the console, do not diagnose the passkey prompts separately: set CONSOLE_ORIGIN on the engine and both clear together.
One caveat on the console message while you do. The console reports this ending as the engine not starting the identity check and suggests trying again in a moment, which is the right advice for the transient version and the wrong advice here. Retrying a 501 never succeeds, because nothing about it is transient. See look up a console error message for the message as it appears and error and status codes for the 501 on the wire.
Where this fits
- Session management covers session lifetime, which is what step-up measures freshness against.
- Roles and capabilities covers the capability check, which runs independently: step-up never grants a permission you do not hold, it only asks you to prove you are present.
Last updated .