> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Step-Up Authentication

> React SDK hooks and UI for step-up authentication — automatic prompts, headless verification, and Dynamic's built-in UI.

<Card title="Recommended: JavaScript SDK with React Hooks" icon="react" href="/javascript/reference/react-quickstart" color="#4779FE">
  For new React apps, we recommend the JavaScript SDK with React Hooks (`@dynamic-labs-sdk/react-hooks`) instead of the legacy React SDK documented here. The JS SDK comes with many benefits such as a much smaller bundle size and other optimizations. Use the [React quickstart (JavaScript SDK)](/javascript/reference/react-quickstart) to get started.
</Card>

The React SDK offers three approaches to step-up authentication, from fully managed to fully custom:

1. **`promptStepUpAuth`** — One call that automatically picks the right verification method and shows Dynamic's built-in UI. **Recommended for most use cases.**
2. **`promptMfa` / `promptReauthenticate`** — Dynamic's built-in UI with explicit control over which verification path is used.
3. **Individual verify methods** — Full headless control for building your own UI.

After verification, the elevated access token is automatically stored and applied to subsequent API calls. **You never need to manually handle the token.**

For concepts, scopes, and token lifecycle, see [Step-up authentication overview](/overview/authentication/step-up-auth).

## Prerequisites

* `DynamicContextProvider` configured with your environment ID
* At least one verification method enabled in your [dashboard security settings](https://app.dynamic.xyz/dashboard/security)
* Step-up authentication enabled for your environment

## Approach 1: Automatic prompt (recommended)

`promptStepUpAuth` checks whether the user has MFA methods and automatically routes to the correct UI — passkey/TOTP for MFA users, OTP/wallet for non-MFA users.

```tsx theme={"system"}
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 or verification failed
      }
    }

    // Token is stored — proceed with the operation.
    // The SDK attaches it to the API call automatically.
    await exportWallet();
  };

  return <button onClick={handleExport}>Export Wallet</button>;
};
```

### How `promptStepUpAuth` routes

| User state               | Verification method              | UI shown                           |
| ------------------------ | -------------------------------- | ---------------------------------- |
| Has passkey + TOTP       | Most recently created MFA method | Passkey prompt or TOTP input       |
| Has passkey only         | Passkey                          | Browser passkey prompt             |
| Has TOTP only            | TOTP                             | Dynamic's TOTP code input          |
| No MFA, has email        | Email OTP                        | Dynamic's re-auth method selection |
| No MFA, has SMS          | SMS OTP                          | Dynamic's re-auth method selection |
| No MFA, has wallet       | Wallet signature                 | Dynamic's re-auth method selection |
| No MFA, has social       | Social OAuth                     | Dynamic's re-auth method selection |
| Multiple non-MFA methods | User selects                     | Dynamic's method chooser           |

## Approach 2: Dynamic's built-in UI (explicit path)

Use `promptMfa` or `promptReauthenticate` when you want to control which verification path is used while still using Dynamic's UI:

<Tabs>
  <Tab title="MFA prompt">
    Shows Dynamic's MFA verification UI (passkey or TOTP). Only works when the user has registered MFA methods.

    ```tsx theme={"system"}
    const { promptMfa } = useStepUpAuthentication();

    await promptMfa({ requestedScopes: [TokenScope.Walletexport] });
    ```
  </Tab>

  <Tab title="Re-auth prompt">
    Shows Dynamic's re-authentication UI (email OTP, SMS OTP, wallet signature, or social OAuth). Works for all users.

    ```tsx theme={"system"}
    const { promptReauthenticate } = useStepUpAuthentication();

    await promptReauthenticate({ requestedScopes: [TokenScope.Credentiallink] });
    ```

    <Warning>
      When MFA is enabled and the user has MFA methods, `promptReauthenticate` will be blocked by the backend. Use `promptStepUpAuth` or `promptMfa` instead.
    </Warning>
  </Tab>
</Tabs>

## Approach 3: Headless (custom UI)

For full control over the UI, use the individual verify methods. You build the UI — the hook handles verification and token storage.

### Checking if step-up is required

#### `checkStepUpAuth` (recommended)

`checkStepUpAuth` is the recommended approach. It performs a server-authoritative check and returns both whether step-up is required and the available credentials, so you can route the user to the right verification method without extra API calls.

```tsx theme={"system"}
const { checkStepUpAuth } = useStepUpAuthentication();

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);
}
```

It does a fast local check first (if an elevated token exists, returns `{ isRequired: false }` without an API call), then calls the backend. On failure, it defaults to `{ isRequired: true }` for safety.

#### `isStepUpRequired` (simple boolean)

Use `isStepUpRequired` if you only need a boolean and don't need the available credentials:

```tsx theme={"system"}
const { isStepUpRequired } = useStepUpAuthentication();

if (await isStepUpRequired({ scope: TokenScope.Walletexport })) {
  // Show your step-up verification UI
}
```

Returns `true` when step-up auth is enabled for the environment and no valid elevated token exists for the scope. Returns `false` if the token already exists or step-up is not enabled.

### Email / SMS OTP

```tsx theme={"system"}
const { sendOtp, verifyOtp } = useStepUpAuthentication();

// Send OTP to the user's sign-in enabled email or SMS credential
const verification = await sendOtp();
// verification.verificationType tells you whether it was 'email' or 'sms'

// After the user enters the code:
await verifyOtp({
  verificationToken: code,
  requestedScopes: [TokenScope.Credentiallink],
});
```

To target a specific credential, pass `credentialId` at call time or when initializing the hook:

```tsx theme={"system"}
// Option 1: pass credentialId at call time (preferred for dynamic selection)
const { sendOtp, verifyOtp } = useStepUpAuthentication();
await sendOtp({ credentialId: 'specific-email-credential-id' });

// Option 2: pass credentialId when initializing the hook
const { sendOtp, verifyOtp } = useStepUpAuthentication({
  credentialId: 'specific-email-credential-id',
});
```

### Wallet signature (external wallets only)

Wallet-based step-up verification is only available for external wallets. Embedded wallets cannot be used for step-up authentication.

```tsx theme={"system"}
const { verifyWallet } = useStepUpAuthentication();

// Pass walletId at call time — defaults to the first connected wallet when omitted
await verifyWallet({
  walletId: walletCredentialId,
  requestedScopes: [TokenScope.Walletexport],
});
```

### Passkey MFA

```tsx theme={"system"}
const { verifyPasskeyMfa } = useStepUpAuthentication();

// Triggers the browser's passkey prompt
await verifyPasskeyMfa({ requestedScopes: [TokenScope.Walletexport] });
```

### TOTP MFA

```tsx theme={"system"}
const { verifyTotpMfa } = useStepUpAuthentication();

await verifyTotpMfa({
  code: totpCode,
  requestedScopes: [TokenScope.Walletexport],
});
```

### Social (OAuth)

```tsx theme={"system"}
import { ProviderEnum } from '@dynamic-labs/sdk-api-core';

const { verifySocial } = useStepUpAuthentication();

// Opens a popup for the user to re-authenticate with their linked social account
await verifySocial({
  provider: ProviderEnum.Google,
  requestedScopes: [TokenScope.Credentiallink],
});
```

The user must have the social account already linked. The SDK opens a popup to the OAuth provider — no redirect is needed. If the browser blocks the popup (e.g., on mobile), the SDK falls back to redirect and resumes the flow automatically on return.

### Recovery code

```tsx theme={"system"}
const { verifyRecoveryCode } = useStepUpAuthentication();

await verifyRecoveryCode({
  code: recoveryCode,
  requestedScopes: [TokenScope.Credentialunlink],
});
```

### Error handling

All methods update the shared `state` object and throw on failure:

```tsx theme={"system"}
const { verifyPasskeyMfa, state, resetState } = useStepUpAuthentication();

try {
  await verifyPasskeyMfa({ requestedScopes: [TokenScope.Walletexport] });
} catch (error) {
  // state.error contains the error message
  // Call resetState() before retrying
}
```

```tsx theme={"system"}
// Render loading and error states
{state.isLoading && <Spinner />}
{state.error && (
  <div>
    <p>{state.error}</p>
    <button onClick={resetState}>Try again</button>
  </div>
)}
```

## Full example: headless credential linking

```tsx theme={"system"}
import { FC, useState } from 'react';
import { useStepUpAuthentication } from '@dynamic-labs/sdk-react-core';
import { TokenScope } from '@dynamic-labs/sdk-api-core';

const LinkEmailButton: FC = () => {
  const { isStepUpRequired, sendOtp, verifyOtp, state, resetState } =
    useStepUpAuthentication();
  const [step, setStep] = useState<'idle' | 'otp'>('idle');
  const [code, setCode] = useState('');

  const handleStart = async () => {
    resetState();

    if (!(await isStepUpRequired({ scope: TokenScope.Credentiallink }))) {
      // Step-up not needed — proceed directly to link
      await linkEmail();
      return;
    }

    await sendOtp();
    setStep('otp');
  };

  const handleVerify = async () => {
    await verifyOtp({
      verificationToken: code,
      requestedScopes: [TokenScope.Credentiallink],
    });
    // Token stored — proceed with linking
    await linkEmail();
    setStep('idle');
  };

  if (step === 'otp') {
    return (
      <div>
        <input
          value={code}
          onChange={(e) => setCode(e.target.value)}
          placeholder="Enter verification code"
        />
        <button onClick={handleVerify} disabled={state.isLoading}>
          {state.isLoading ? 'Verifying...' : 'Verify'}
        </button>
        {state.error && <p>{state.error}</p>}
      </div>
    );
  }

  return <button onClick={handleStart}>Link Email</button>;
};
```

## Hook reference

See the [full `useStepUpAuthentication` reference](/react/reference/hooks/login-user-management/usestepupauthentication) for all return values and parameter types.

## External auth (Bring Your Own Auth)

If you use [external auth (BYOA)](/overview/authentication/bring-your-own-auth), your backend can issue elevated access tokens directly using the `useExternalAuth` hook — no user interaction required. See the [External Auth Step-Up guide](/react/authentication-methods/step-up-auth/external-auth).

## Related

* [Step-Up Authentication Overview](/overview/authentication/step-up-auth) — Concepts, scopes, token lifecycle
* [External Auth Step-Up](/react/authentication-methods/step-up-auth/external-auth) — Backend-issued elevated tokens for BYOA
* [Action-Based MFA](/react/authentication-methods/mfa/action-based)
* [MFA Overview](/react/authentication-methods/mfa/overview)
