Skip to main content

Summary

Hook for step-up authentication — requiring users to re-verify their identity before sensitive actions. Supports three usage patterns:
  • Automatic promptspromptStepUpAuth auto-selects MFA or re-auth and shows Dynamic’s UI. promptMfa and promptReauthenticate offer explicit path control.
  • Headless methodssendOtp, verifyOtp, verifyWallet, verifySocial, verifyPasskeyMfa, verifyTotpMfa, verifyRecoveryCode for building custom UIs.
After successful verification with requestedScopes, the elevated access token is stored in SDK state and automatically applied to subsequent API calls that require the scope. Must be used within a child of DynamicContextProvider.

Parameters

ParameterTypeDescription
credentialIdstring (optional)Target a specific credential for OTP verification at hook level. Defaults to the first sign-in enabled email or SMS credential. Can also be passed at call time via sendOtp({ credentialId }).

Return values

PropertyTypeDescription
isStepUpRequired({ scope: TokenScope }) => Promise<boolean>Returns true if step-up auth is enabled and no valid elevated token exists for the scope
checkStepUpAuth({ scope: TokenScope }) => Promise<StepUpCheckResponse>Recommended. Server-authoritative check that returns { isRequired, credentials, defaultCredentialId }. The defaultCredentialId is the recommended credential to authenticate with. Defaults to required on failure.
promptStepUpAuth(params?) => Promise<string | undefined>Auto-routes to MFA or re-auth, shows Dynamic’s built-in UI
promptMfa(params?) => Promise<string | undefined>Shows Dynamic’s MFA UI (passkey or TOTP). Requires user to have registered MFA methods.
promptReauthenticate(params?) => Promise<string | undefined>Shows Dynamic’s re-auth UI (OTP or wallet). Blocked when user has MFA enabled.
sendOtp(params?) => Promise<OTPVerification | null>Sends OTP to the user’s sign-in enabled credential. Accepts optional { credentialId } to target a specific credential at call time. Returns { verificationType, credentialId }.
verifyOtp(params: VerifyOtpParams) => Promise<void>Verify an OTP code with requested scopes
verifyWallet(params: VerifyWalletParams) => Promise<void>Verify via external wallet signature. Accepts optional walletId to target a specific wallet. Defaults to the first connected wallet. Embedded wallets are not supported.
verifySocial(params: VerifySocialParams) => Promise<void>Verify via a linked social account (OAuth popup)
verifyPasskeyMfa(params: VerifyPasskeyMfaParams) => Promise<string | undefined>Verify via passkey (WebAuthn). Triggers browser passkey prompt.
verifyTotpMfa(params: VerifyTotpMfaParams) => Promise<string | undefined>Verify via TOTP authenticator app code
verifyRecoveryCode(params: VerifyRecoveryCodeParams) => Promise<string | undefined>Verify via backup recovery code
state{ isLoading: boolean, error: string | null }Loading and error state for headless methods
resetState() => voidReset loading and error state. Call before retrying.

Prompt parameter types

type PromptStepUpAuthParams = {
  requestedScopes?: TokenScope[];
};

// promptMfa and promptReauthenticate accept the same shape

Headless parameter types

type VerifyOtpParams = {
  verificationToken: string;
  requestedScopes?: TokenScope[];
};

type SendOtpParams = {
  credentialId?: string;
};

type VerifyWalletParams = {
  walletId?: string;
  requestedScopes?: TokenScope[];
};

type VerifySocialParams = {
  provider: ProviderEnum;
  requestedScopes?: TokenScope[];
};

type VerifyPasskeyMfaParams = {
  requestedScopes?: TokenScope[];
};

type VerifyTotpMfaParams = {
  code: string;
  deviceId?: string;
  requestedScopes?: TokenScope[];
};

type VerifyRecoveryCodeParams = {
  code: string;
  requestedScopes?: TokenScope[];
};

Usage: automatic prompt

import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';

const ExportButton = () => {
  const { isStepUpRequired, promptStepUpAuth } = useStepUpAuthentication();

  const handleExport = async () => {
    if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
      try {
        await promptStepUpAuth({ requestedScopes: [TokenScope.Walletexport] });
      } catch {
        return; // User cancelled
      }
    }
    // Token stored — proceed with the operation
    await exportWallet();
  };

  return <button onClick={handleExport}>Export Wallet</button>;
};
checkStepUpAuth is the recommended way to determine whether step-up is required. It performs a server-authoritative check and returns the available credentials, so you can route the user to the right verification method without extra API calls.
import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';

const SecureAction = () => {
  const { checkStepUpAuth, promptStepUpAuth } = useStepUpAuthentication();

  const handleAction = async () => {
    const { isRequired, credentials, defaultCredentialId } =
      await checkStepUpAuth({ scope: TokenScope.Walletexport });

    if (isRequired) {
      // credentials contains the available verification methods
      // Each credential has: { id, format, type?, alias? }
      //
      // defaultCredentialId is the recommended credential to authenticate with:
      // - For MFA users: the configured default device or most recently added
      // - For re-auth users: the credential used to sign in
      const defaultCred = credentials.find((c) => c.id === defaultCredentialId);

      try {
        await promptStepUpAuth({
          requestedScopes: [TokenScope.Walletexport],
        });
      } catch {
        return; // User cancelled
      }
    }

    await performSecureAction();
  };

  return <button onClick={handleAction}>Secure Action</button>;
};

Usage: headless OTP

import { useState } from 'react';
import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';

const StepUpOtp = ({ onComplete }: { onComplete: () => void }) => {
  const { sendOtp, verifyOtp, state, resetState } = useStepUpAuthentication();
  const [code, setCode] = useState('');

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        resetState();
        await verifyOtp({
          verificationToken: code,
          requestedScopes: [TokenScope.Credentiallink],
        });
        onComplete();
      }}
    >
      <button type="button" onClick={() => sendOtp()}>Send Code</button>
      <input value={code} onChange={(e) => setCode(e.target.value)} />
      <button type="submit" disabled={state.isLoading}>
        {state.isLoading ? 'Verifying...' : 'Verify'}
      </button>
      {state.error && <p>{state.error}</p>}
    </form>
  );
};