Skip to main content

What you’ll build

Step-up authentication gates a sensitive action behind fresh authentication, scoped to a TokenScope such as wallet:sign or wallet:export. You’ll:
  1. Check whether the action’s scope currently needs step-up.
  2. Re-authenticate the user, passing that scope as requestedScopes. This mints a short-lived elevated access token for the scope.
  3. Perform the action — the SDK finds and consumes the matching elevated token automatically.
Step-up is independent of how the user re-authenticates. Email OTP, SMS OTP, an authenticator app, a passkey, a recovery code, or a connected wallet all satisfy it the same way — you pass requestedScopes on the verify/authenticate call. This page uses email OTP for the full walkthrough, then shows the one-line change for every other method. If your backend authorizes the action instead of the user, an externally-issued JWT elevates the session server-side.
This is not the same as MFA. MFA (see MFA enrollment & management) is one credential type a user can present. Step-up is the scope-based elevation flow — your project settings decide whether a given scope requires it, and any credential the user has can satisfy it.

Prerequisites

  • The user is already signed in. Step-up elevates an existing session; it is not a sign-in flow.

Checking whether step-up is required

This page gates signing as the running example, so every call below checks the wallet:sign scope. Swap it for whichever scope matches the action you’re protecting — the flow is identical. checkStepUpAuth({ scope }) returns { isRequired, credentials, defaultCredentialId }. It resolves to isRequired: false immediately when a valid elevated token for the scope already exists; otherwise it asks the backend, which factors in your project settings. credentials lists the methods this user can use to satisfy the challenge, and defaultCredentialId is the one to pre-select in your UI (the sign-in credential, or the default / most-recently-added MFA device) — use it instead of blindly taking the first credential.

Full example: elevate with email OTP

Check the scope, re-authenticate only when needed, then perform the action. The sign call needs no elevated token passed to it — it reads the one that verifyOTP just minted.

Use any auth method

Every credential type takes the same requestedScopes argument. Swap the re-authentication step above for whichever the user has — the check-then-act structure stays identical. Each snippet below is just that one call.

Server-authorized step-up (external JWT)

The methods above elevate the session from something the user does in the browser. When your backend is the one authorizing the action — for example a server-side approval workflow, or trust already established by your own system — you elevate with an assertion JWT instead. Here the scope is not passed as requestedScopes; it travels inside the signed JWT, so the browser can’t widen it. How it works:
  1. Your backend mints a short-lived JWT and signs it with the key registered at your environment’s external auth JWKS URL. The JWT carries these claims:
    • sub — the user the elevation is for.
    • scope — the TokenScope being granted (e.g. wallet:sign). This is what determines the elevated scope.
    • jti — a unique id so the assertion can only be redeemed once.
    • exp — a short expiry.
  2. Hand that JWT to the SDK. It verifies the signature against your JWKS, then mints and stores the elevated access token for the scope claim.
  3. Perform the action — the SDK consumes the matching elevated token exactly as with the user-driven methods.
Because the scope is baked into the signed assertion, you don’t call checkStepUpAuth first for this path — your backend has already decided the elevation is warranted.

Available scopes

Request the scope that matches the action you’re gating. Common SDK scopes:

Handling errors

Step-up adds no error types of its own. Because any credential can satisfy the challenge, the errors you handle are the ones the re-authentication method you chose already throws — a wrong code, a rate-limited attempt, a rejected wallet signature — handled exactly as they are outside step-up. For example, the MFA methods (authenticateTotpMfaDevice, authenticatePasskeyMFA, authenticateMfaRecoveryCode) throw MfaInvalidOtpError on a wrong or expired code and MfaRateLimitedError after too many attempts; ask the user to re-enter or wait, and don’t run the action. Two situations are specific to step-up itself:
checkStepUpAuth defaults to isRequired: true if the check itself fails, so a network error never silently skips the challenge. Gate the action on the check result, not on the UI.

See also

Last modified on July 22, 2026