Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.prelude.so/llms.txt

Use this file to discover all available pages before exploring further.

Step-up authentication lets you require additional proof from an already-authenticated user before granting access to sensitive operations. Instead of re-authenticating from scratch, the user completes a challenge (SMS OTP, email OTP, etc.) and receives a time-limited scope on their session.

Prerequisites

  • A Prelude account with access to the Auth API
  • An Application ID (appID) — see Applications
  • Your Management API key for backend calls
  • A working login flow (users must be authenticated before requesting step-up)

How it works

When a user requests a sensitive action, your frontend asks Prelude for a scope grant. Prelude calls a hook on your backend, where you decide whether to grant the scope immediately, require a challenge, or block the request entirely. If you require a challenge, Prelude walks the user through each step you defined. Prelude natively handles managed steps like verify_sms and verify_email — no extra work on your side. Once every step is completed, the scope is granted to the user’s access token.
You can also define custom steps handled by your own backend (e.g. KYC review, biometric check). See Custom Steps for details.

Configure step-up

1

Create a step-up configuration

Register each scope you want to expose and how its decision is produced. Use mode: "delegated" to call your delegation hook, or mode: "direct" to serve a static decision without any hook call.
curl -X POST https://api.prelude.dev/v2/session/apps/${APP_ID}/config/stepup \
  -H "Authorization: Bearer ${MANAGEMENT_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "jwks_url": "https://api.example.com/.well-known/jwks.json",
    "step_keys": [],
    "allowed_scopes": [
      {
        "scope": "transfer:write",
        "mode": "delegated",
        "delegated": {
          "delegation_hook": "https://api.example.com/hooks/stepup"
        }
      },
      {
        "scope": "payment:confirm",
        "mode": "delegated",
        "delegated": {
          "delegation_hook": "https://api.example.com/hooks/stepup"
        }
      }
    ]
  }'
FieldRequiredDescription
jwks_urlWhen any scope uses delegated modeYour JWKS endpoint. Used to verify verification tokens issued by your backend for custom steps.
step_keysYesCustom step keys for client-owned steps. Leave empty if you only use managed steps.
allowed_scopes[].scopeYesThe scope name.
allowed_scopes[].modeYesdelegated to call your delegation hook, direct to serve a static decision.
allowed_scopes[].delegated.delegation_hookWhen mode is delegatedThe URL Prelude calls when this scope is requested. See Step-Up Hook.
allowed_scopes[].directWhen mode is directThe static decision to return. See Change Password for an end-to-end direct example.
Scope names must only contain: lowercase letters, uppercase letters, numbers, and the characters .-_:.
A scope may appear more than once in allowed_scopes. Multiple direct entries are allowed as long as each (scope, identifier_type) pair is unique — useful when the step differs per identifier type. At most one delegated entry per scope is allowed; when combined with direct entries, it is used as a fallback if no direct entry matches the user’s identifier types at runtime.
2

Implement the hook endpoint

Your backend must expose the hook URL you registered. When Prelude receives a step-up request, it calls your hook with user and session context. Your hook decides whether to grant, challenge, or block.See the full Step-Up Hook Reference for the request/response format and constraints.

The step-up flow

1. Request a scope grant

The frontend SDK initiates the flow by calling requestStepUp with a scope. Prelude calls your hook and returns one of:
StatusMeaning
continueScope granted immediately, no challenge needed. The SDK refreshes the session automatically.
reviewA challenge was created. The user must complete all steps.
blockScope denied.

2. Complete managed steps

Prelude handles the following step types natively:
Step keyDescription
verify_smsSMS OTP verification sent to the user’s phone number.
verify_emailEmail OTP verification sent to the user’s email address.
The SDK provides startOTP, checkOTP, and retryOTP methods to drive these steps using the challengeId from the onChallenge callback.

3. Automatic completion

When the last step is completed, the SDK automatically refreshes the session with the granted scope. The new access token behavior depends on the grant_mode set by your hook:
Grant modeBehavior
single-useThe scope is attached only to this access token and expires after granted_for seconds. It is not persisted on the session.
session-boundThe scope is stored on the session and included in every subsequent refresh for granted_for seconds.
See the Web SDK Step-Up guide for the full integration with code examples.

Constraints and validation

Field format

All external fields (scopes, step keys, metadata keys) must match:
a-z A-Z 0-9 . - _ :
No other characters are allowed.

Metadata

RuleLimit
Maximum number of fields5
Maximum key length12 characters
Maximum value length32 characters

Hook response

RuleLimit
Maximum response size64 KB
granted_forIn seconds. Must be between 0 and 86400 (24 hours). Cannot be negative.
granted_for defaultWhen grant_mode is session-bound and granted_for < 1, it defaults to 600 seconds (10 minutes). When grant_mode is single-use, granted_for must be at least 1.
Hook timeout5 seconds. Your endpoint must respond within this time.
Step expiration_durationIn seconds. Must be between 0 and 86400 (24 hours). Cannot be negative.

Step-Up JWKS

Prelude exposes a dedicated JWKS endpoint for step-up token verification at:
https://<app_id>.session.prelude.dev/.well-known/step-up-jwks.json
This is separate from the main JWKS endpoint used for access token verification.

Security recommendations

Anti-replay: When your backend receives a request carrying a scope granted via step-up, you should allow each scoped token to be used only once. Track the token’s jti claim and reject any replayed token. This prevents an attacker from reusing a captured token to repeat a sensitive action.
  • Limit scope lifetime: Use the granted_for field to keep scoped access as short-lived as practical. A transfer confirmation might only need 60 seconds.
  • Use grant_mode: "single-use" for high-sensitivity operations (e.g. fund transfers). This ensures the scope is attached to a single access token and not persisted on the session.
  • Validate signals in your hook: Prelude sends user_agent, platform, and ip in the hook request. Use these to detect suspicious context changes (e.g. a different IP than the original login).

What’s next?

Custom Steps

Add client-owned steps like KYC review or biometric checks to your challenges.

Step-Up Hook

Full API reference for the hook endpoint your backend must implement.