Skip to content

Adaptive step-up

Step-up is when the platform demands MFA in the middle of a session. Even though the user signed in already (with or without MFA), specific events or conditions trigger a fresh challenge. The user completes MFA again; the session's AAL is bumped; the action proceeds.

Step-up is the dial between "MFA every sign-in" (annoying for users) and "MFA never" (insufficient for sensitive actions). It targets specifically the moments that matter.

MFA → Adaptive step-up.

You define policies. Each policy has:

  • Trigger — what condition / event fires the step-up.
  • Required factor — which factor satisfies (any enrolled / specifically WebAuthn / specifically not SMS).
  • AAL target — what AAL the session should reach after the step-up completes.
  • Duration — how long the elevated AAL persists before re-prompting.

The policies stack — multiple triggers can each fire step-up; the most-restrictive applicable AAL wins.

Specific user actions that demand step-up:

  • Sensitive operations — changing primary email, deleting account, generating an API key. Configure per operation; the user-facing app or your code triggers the AAL check.
  • Admin tenant actions — performing high-risk admin operations (deleting an application, force-resetting another user's MFA, exporting users). Defaults to step-up for these.
  • First time accessing a resource class — first time the user opens "Payments" in the app, step up. Subsequent opens within the duration don't re-prompt.

The platform's risk engine scores every sign-in attempt. Configure thresholds:

  • Risk score ≥ X → demand step-up before letting the session proceed.
  • From a new device → step-up on first sign-in from each new device fingerprint.
  • From a new country → step-up when the user signs in from a country they haven't signed in from in the last N days.
  • Geographic velocity — sign-in from one city, then sign-in from another city 5 minutes later (impossible travel) → step-up.
  • Threat intel hit — the user's IP appeared in a threat feed → step-up.

The risk engine is on by default; configure scoring + thresholds in Threat intelligence.

After step-up succeeds, the elevated AAL persists for the configured duration. Common values:

  • 15 minutes — for AAL 2 in routine sensitive operations.
  • 5 minutes — for AAL 3 in highly sensitive operations.

Tighter = more friction for power users doing many sensitive actions in a row. Looser = a stolen access token has a longer effectiveness window. The 15-minute default is a sensible balance.

A user opens the "Change email" page in your app. Your app calls the API. The API returns 401 step_up_required with required_acr: aal2. The SDK reads this, shows the user a "Confirm your identity" prompt with their already-enrolled factor (passkey, TOTP, etc.). User confirms. SDK gets a fresh access token with the elevated AAL. Original API call retries; succeeds.

From the user's perspective, one extra "Confirm with Touch ID" tap inserted into the flow. The whole round trip is sub-2-seconds for WebAuthn; longer for TOTP or SMS.

Your applications need to honour the step-up contract:

  • Backend: check the access token's acr claim on sensitive endpoints. If insufficient, return 401 with the step_up_required envelope.
  • Frontend: catch 401s with that envelope and call the SDK's stepUp method.

See Step-up authentication (developer reference) for the full integration.

Step-up policies live primarily at the tenant level. Individual applications can override:

  • "Tenant default: step-up on sensitive operations after 15 min."
  • "Application X: step-up on EVERY sensitive operation, no AAL caching."

Override in the application's authentication policy tab.

Step-up prompts have a friction cost. The right copy makes them feel protective rather than annoying:

  • "Confirm your identity before changing your email."
  • "You're signing into Finance from a new device — let's verify."
  • "We need to re-verify for this sensitive action."

Avoid "for security reasons" — too vague. Specific framing of WHY the prompt is appearing makes users tolerate it (and trust the platform more).

Some teams want "step-up via SMS is enough for low-sensitivity operations; only WebAuthn for high-sensitivity." Configure per-policy:

  • "Sensitive ops" policy → required factor: any enrolled. AAL 2.
  • "Highly sensitive ops" policy → required factor: WebAuthn or TOTP only (no SMS). AAL 3.

The user's existing factors determine which AAL is reachable. A user with only SMS can satisfy AAL 2 (SMS-enrolled session is AAL 2) but not AAL 3 (SMS doesn't satisfy AAL 3 by policy). The platform refuses the action; your application's frontend shows "this operation requires a stronger factor. Set up WebAuthn or TOTP to proceed."

Every step-up records:

  • auth.step_up_initiated — the platform triggered.
  • auth.step_up_completed — succeeded.
  • auth.step_up_failed — user dismissed or failed.
  • auth.step_up_skipped — the policy didn't fire because the session already had sufficient AAL.

Filter audit on these to investigate "did step-up actually fire when it should have?" patterns.

Step-up has a real cost. Reserve it for things that matter:

  • Changing email or phone.
  • Generating an API key.
  • Payouts above a threshold.
  • Modifying MFA factors.
  • Admin operations on other users.

Don't step-up:

  • Routine reads.
  • Preference changes.
  • Anything the user can undo themselves.

Over-stepping trains users to dismiss prompts reflexively, which weakens the prompt's signal when you really need it.