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

# MFA Enrollment

> [card] Recommended: JavaScript SDK with React Hooks
>
> 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)](https://docs.dynamic.xyz/docs/javascript/reference/react-quickstart.md) to get started.

## Console Setup

MFA enrollment requires users to register a TOTP device or Passkey. Configure it in the Dynamic Console before implementing in your application. See [End-User MFA — Enrollment](https://docs.dynamic.xyz/docs/platform/console/end-user-mfa.md#enrollment) for console setup instructions.

## Your UI SDK Implementation

> [tab] TOTP
>
> * **Trigger gating**: Check `userWithMissingInfo?.scope` for `requiresAdditionalAuth`.
> * **Detect MFA**: Use `useSyncMfaFlow` to know when to prompt.
> * **Add device**: Call `addDevice()` → show QR from `uri` (install `qrcode`).
> * **Verify**: `authenticateDevice({ code })` → fetch and show recovery codes → `completeAcknowledgement()`.
> * **Refresh**: Call `getUserDevices()` after verification.
>
> ```tsx theme={"system"}
> import { useMfa, useSyncMfaFlow, useDynamicContext } from "@dynamic-labs/sdk-react-core";
>
> export function AccountTotpMfa() {
>   const {
>     addDevice,
>     authenticateDevice,
>     getUserDevices,
>     getRecoveryCodes,
>     completeAcknowledgement,
>   } = useMfa();
>   const { userWithMissingInfo } = useDynamicContext();
>
>   useSyncMfaFlow({
>     handler: async () => {
>       if (userWithMissingInfo?.scope?.includes("requiresAdditionalAuth")) {
>         const devices = await getUserDevices();
>         if (devices.length === 0) {
>           const { uri } = await addDevice();
>           // Render QR from `uri`, then prompt for OTP
>           return;
>         }
>         // Prompt for OTP
>       } else {
>         // MFA already satisfied; show recovery codes if needed
>         const codes = await getRecoveryCodes();
>         // Show `codes` to the user, then:
>         await completeAcknowledgement();
>       }
>     },
>   });
>
>   const verifyOtp = async (code: string) => {
>     await authenticateDevice({ code });
>     const codes = await getRecoveryCodes();
>     // Show `codes` to the user, then:
>     await completeAcknowledgement();
>     // Refresh device list
>     await getUserDevices();
>   };
>
>   return null;
> }
> ```
>
> **Only one verified TOTP device per user**
>
> * Recovery codes are single-use. Regenerate with `getRecoveryCodes(true)` if needed.
>
> **Troubleshooting**
>
> * Error: "401 Unauthorized" when adding a second TOTP device — only one device is supported; delete the existing device first.
> * QR code not displaying — ensure `qrcode` is installed: `npm install qrcode @types/qrcode`.
> * Recovery codes not working — each code is single-use; generate new codes if exhausted.

> [tab] Passkey
>
> * **Trigger gating**: Check `userWithMissingInfo?.scope` for `requiresAdditionalAuth`.
> * **Detect MFA**: Use `useSyncMfaFlow`.
> * **Register or authenticate**: If no passkeys → `registerPasskey()`, else `authenticatePasskeyMFA()`.
> * **Recovery**: After success, `getRecoveryCodes()` → show → `completeAcknowledgement()`.
> * **Refresh**: Call `getPasskeys()` after registration/authentication.
>
> ```tsx theme={"system"}
> import {
>   useSyncMfaFlow,
>   useMfa,
>   useDynamicContext,
> } from "@dynamic-labs/sdk-react-core";
> import { useRegisterPasskey, useAuthenticatePasskeyMFA, useGetPasskeys } from "@dynamic-labs/sdk-react-core";
>
> export function AccountPasskeyMfa() {
>   const { registerPasskey } = useRegisterPasskey();
>   const { authenticatePasskeyMFA } = useAuthenticatePasskeyMFA();
>   const { getPasskeys } = useGetPasskeys();
>   const { getRecoveryCodes, completeAcknowledgement } = useMfa();
>   const { userWithMissingInfo } = useDynamicContext();
>
>   useSyncMfaFlow({
>     handler: async () => {
>       if (userWithMissingInfo?.scope?.includes("requiresAdditionalAuth")) {
>         const keys = await getPasskeys();
>         if (keys.length === 0) {
>           await registerPasskey();
>         } else {
>           await authenticatePasskeyMFA();
>         }
>         // Refresh passkey list
>         await getPasskeys();
>         const codes = await getRecoveryCodes();
>         // Show `codes` to the user, then:
>         await completeAcknowledgement();
>       } else {
>         // MFA already satisfied; show recovery codes if needed
>         const codes = await getRecoveryCodes();
>         // Show `codes` to the user, then:
>         await completeAcknowledgement();
>       }
>     },
>   });
>
>   return null;
> }
> ```
>
> * Passkeys MFA is currently in closed beta.
> * Requires WebAuthn support; production must be served over HTTPS.
>
> **Troubleshooting**
>
> * Error: "Passkey not supported" — check browser compatibility; requires WebAuthn and HTTPS in production.
> * Recovery codes not working — each recovery code is single-use; generate new codes if exhausted.

## Dynamic UI Implementation

With the Dynamic UI, MFA enrollment prompts appear automatically during onboarding/login when additional authentication is required. To explicitly trigger in your app, use `useSyncMfaFlow` to detect the requirement and `usePromptMfaAuth` to open the UI.

Note: The Dynamic UI is method-agnostic. It automatically prompts with whichever MFA method(s) you have enabled (TOTP and/or Passkeys), so separate TOTP/Passkey tabs are not required here.

```tsx theme={"system"}
import { useSyncMfaFlow, useDynamicContext, usePromptMfaAuth } from "@dynamic-labs/sdk-react-core";

const { userWithMissingInfo } = useDynamicContext();
const promptMfaAuth = usePromptMfaAuth();

useSyncMfaFlow({
  handler: async () => {
    if (userWithMissingInfo?.scope?.includes("requiresAdditionalAuth")) {
      // Opens the Dynamic UI to complete MFA enrollment
      await promptMfaAuth(); // No MFA token needed for enrollment
    }
  },
});
```

* [useSyncMfaFlow](https://docs.dynamic.xyz/docs/react/reference/hooks/login-user-management/usesyncmfaflow.md) hook to detect the requirement and open the Dynamic UI
* [usePromptMfaAuth](https://docs.dynamic.xyz/docs/react/reference/hooks/login-user-management/usepromptmfa.md) hook to prompt the user to authenticate with MFA

## Programmatic MFA Modal Controls

Dynamic SDKs provide granular control over individual MFA modal screens. These methods allow you to programmatically show and hide specific MFA screens in your application.

### Available Modal Controls

For React applications, use the `useDynamicModals` hook to control MFA modal visibility:

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

export function MfaModalControls() {
  const {
    setShowMFAManage,
    setShowMfaChooseType,
    setShowMfaQRCode,
    setShowOTPVerification,
    setShowMfaViewBackupCodes,
    setShowMfaEnterBackupCodes,
  } = useDynamicModals();

  return (
    <div>
      <button onClick={() => setShowMFAManage(true)}>
        Show MFA Management
      </button>
      <button onClick={() => setShowMfaChooseType(true)}>
        Show MFA Type Selection
      </button>
      <button onClick={() => setShowMfaQRCode(true)}>
        Show QR Code
      </button>
      <button onClick={() => setShowOTPVerification(true)}>
        Show OTP Verification
      </button>
      <button onClick={() => setShowMfaViewBackupCodes(true)}>
        Show Backup Codes
      </button>
      <button onClick={() => setShowMfaEnterBackupCodes(true)}>
        Show Enter Backup Codes
      </button>
    </div>
  );
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.