> ## 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 title="Recommended: JavaScript SDK with React Hooks" icon="react" 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>

## Dashboard Setup

MFA enrollment requires users to register a TOTP device or Passkey. Configure it in the dashboard before implementing in your application. See [End-User MFA - Enrollment](/overview/developer-dashboard/end-user-mfa#enrollment) for dashboard setup instructions.

## Your UI SDK Implementation

<Tabs>
  <Tab title="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>

  <Tab title="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.
  </Tab>
</Tabs>

## 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](/react/reference/hooks/login-user-management/usesyncmfaflow) hook to detect the requirement and open the Dynamic UI
* [usePromptMfaAuth](/react/reference/hooks/login-user-management/usepromptmfa) 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>
  );
}
```
