> ## 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

## 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">
    * **Add device**: Call `client.mfa.addDevice(MFADeviceType.Totp)` → show secret for manual entry.

      * **Verify**: `client.mfa.verifyDevice(code, MFADeviceType.Totp)` completes setup.
      * **Manage devices**: `client.mfa.getUserDevices()` lists devices.
      * **Recovery codes**: `client.mfa.getRecoveryCodes(true)` regenerates backup codes.

      ```typescript theme={"system"}
      import React, { useState } from 'react';
      import { Alert, View, Text, TextInput, Button } from 'react-native';
      import { MFADeviceType } from '@dynamic-labs/sdk-api-core';
      import * as Clipboard from 'expo-clipboard';
      import { useDynamic } from './path-to-your-client';

      export function AccountTotpMfa() {
        const client = useDynamic();
        const [step, setStep] = useState<'setup' | 'verify'>('setup');
        const [deviceInfo, setDeviceInfo] = useState<{
          id: string;
          secret: string;
        } | null>(null);
        const [verificationCode, setVerificationCode] = useState('');
        const [loading, setLoading] = useState(false);

        const handleAddDevice = async () => {
          try {
            setLoading(true);
            const result = await client.mfa.addDevice(MFADeviceType.Totp);
            setDeviceInfo(result);
            setStep('verify');
          } catch (error) {
            console.error('Failed to add MFA device:', error);
            Alert.alert('Error', 'Failed to add MFA device');
          } finally {
            setLoading(false);
          }
        };

        const handleVerifyDevice = async () => {
          if (!deviceInfo || !verificationCode.trim()) {
            Alert.alert('Error', 'Please enter the verification code');
            return;
          }

          try {
            setLoading(true);
            await client.mfa.verifyDevice(verificationCode, MFADeviceType.Totp);
            Alert.alert('Success', 'MFA device added successfully');
          } catch (error) {
            console.error('Failed to verify MFA device:', error);
            Alert.alert(
              'Error',
              'Failed to verify MFA device. Please check your code and try again.',
            );
          } finally {
            setLoading(false);
          }
        };

        if (step === 'setup') {
          return (
            <View>
              <Text>Set up Authenticator App</Text>
              <Button
                title="Generate Secret"
                onPress={handleAddDevice}
                disabled={loading}
              />
            </View>
          );
        }

        return (
          <View>
            <Text>Copy Secret: {deviceInfo?.secret}</Text>
            <Button
              title="Copy Secret"
              onPress={() => Clipboard.setStringAsync(deviceInfo!.secret)}
            />
            <TextInput
              placeholder="Enter 6-digit code"
              value={verificationCode}
              onChangeText={setVerificationCode}
              keyboardType="numeric"
              maxLength={6}
            />
            <Button
              title="Verify & Add Device"
              onPress={handleVerifyDevice}
              disabled={loading}
            />
          </View>
        );
      }
      ```

      **Only one verified TOTP device per user**

      * Recovery codes are single-use. Regenerate with `client.mfa.getRecoveryCodes(true)` if needed.

      **Troubleshooting**

      * Error: "401 Unauthorized" when adding a second TOTP device — only one device is supported; delete the existing device first.
      * Recovery codes not working — each code is single-use; generate new codes if exhausted.
  </Tab>

  <Tab title="Passkey">
    * **Register or authenticate**: If no passkeys → `client.passkeys.register()`, else `client.passkeys.authenticateMFA()`.

      * **Manage**: `client.passkeys.get()` lists passkeys.
      * **Recovery**: After success, `client.mfa.getRecoveryCodes()` → show → `client.mfa.completeAcknowledgement()`.
      * **Refresh**: Call `client.passkeys.get()` after registration/authentication.

      ```typescript theme={"system"}
      import React, { useState, useEffect } from 'react';
      import { Alert, View, Button, ActivityIndicator } from 'react-native';
      import { useDynamic } from './path-to-your-client';

      export function AccountPasskeyMfa() {
        const client = useDynamic();
        const [passkeys, setPasskeys] = useState([]);
        const [loading, setLoading] = useState(false);
        const [step, setStep] = useState<'check' | 'recovery'>('check');

        useEffect(() => {
          checkPasskeys();
        }, []);

        const checkPasskeys = async () => {
          try {
            const keys = await client.passkeys.get();
            setPasskeys(keys);
          } catch (error) {
            console.error('Failed to load passkeys:', error);
          }
        };

        const handleMfaFlow = async () => {
          try {
            setLoading(true);

            // Check if user has passkeys
            const keys = await client.passkeys.get();
            
            if (keys.length === 0) {
              // Register a new passkey
              await client.passkeys.register();
              // Refresh passkey list
              await checkPasskeys();
            } else {
              // Authenticate with existing passkey
              await client.passkeys.authenticateMFA();
            }

            // Get recovery codes and show them
            const recoveryCodes = await client.mfa.getRecoveryCodes();
            setStep('recovery');
            // Show recovery codes to the user
            Alert.alert(
              'Recovery Codes',
              `Your recovery codes:\n${recoveryCodes.join('\n')}\n\nSave these codes in a safe place.`,
              [
                {
                  text: 'I\'ve Saved Them',
                  onPress: async () => {
                    await client.mfa.completeAcknowledgement();
                    setStep('check');
                  },
                },
              ]
            );
          } catch (error) {
            console.error('Failed to complete MFA flow:', error);
            Alert.alert('Error', 'Failed to complete MFA authentication');
          } finally {
            setLoading(false);
          }
        };

        if (loading) {
          return (
            <View style={{ padding: 20 }}>
              <ActivityIndicator size="large" />
            </View>
          );
        }

        return (
          <View style={{ padding: 20 }}>
            <Button
              title={
                passkeys.length === 0
                  ? 'Register Passkey'
                  : 'Authenticate with Passkey'
              }
              onPress={handleMfaFlow}
              disabled={loading}
            />
          </View>
        );
      }
      ```

      * Passkeys require platform setup. See the [React Native Passkey Setup guide](/react-native/reference/setup-passkey).
      * Recovery codes are single-use. Regenerate with `client.mfa.getRecoveryCodes(true)` if needed.

      **Troubleshooting**

      * Error: "Passkey not supported" — verify platform setup and passkey support.
      * Recovery codes not working — each recovery code is single-use; generate new codes if exhausted.
  </Tab>
</Tabs>

## Dynamic UI Implementation

The React Native SDK provides a UI method to prompt MFA authentication. Use `client.ui.mfa.show()` to open the MFA authentication flow.

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

```typescript theme={"system"}
import React, { useState } from 'react';
import { Alert, View, Button } from 'react-native';
import { useDynamic } from './path-to-your-client';

export function MfaAuthPrompt() {
  const client = useDynamic();
  const [loading, setLoading] = useState(false);

  const handlePromptMfaAuth = async () => {
    try {
      setLoading(true);
      // Opens the Dynamic UI to complete MFA enrollment
      await client.ui.mfa.show();
      Alert.alert('Success', 'MFA authentication completed successfully');
    } catch (error) {
      console.error('Failed to prompt MFA auth:', error);
      Alert.alert('Error', 'Failed to prompt MFA authentication');
    } finally {
      setLoading(false);
    }
  };

  return (
    <View>
      <Button
        title="Prompt MFA Authentication"
        onPress={handlePromptMfaAuth}
        disabled={loading}
      />
    </View>
  );
}
```

**Optional: Create MFA token**

You can optionally request an MFA token by passing `createMfaToken: true`:

```typescript theme={"system"}
const mfaToken = await client.ui.mfa.show({ createMfaToken: true });
if (mfaToken) {
  // Use the MFA token for sensitive operations
  console.log('MFA token:', mfaToken);
}
```

## 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

**Show and hide specific MFA screens:**

```typescript theme={"system"}
import React from 'react';
import { View, Button } from 'react-native';
import { dynamicClient } from './path-to-your-client';

export function MfaModalControls() {
  return (
    <View>
      {/* MFA Management Screen */}
      <Button
        title="Show MFA Management"
        onPress={() => dynamicClient.ui.mfa.manage.show()}
      />
      <Button
        title="Hide MFA Management"
        onPress={() => dynamicClient.ui.mfa.manage.hide()}
      />

      {/* MFA Type Selection Screen */}
      <Button
        title="Show MFA Type Selection"
        onPress={() => dynamicClient.ui.mfa.chooseType.show()}
      />
      <Button
        title="Hide MFA Type Selection"
        onPress={() => dynamicClient.ui.mfa.chooseType.hide()}
      />

      {/* QR Code Screen (for TOTP setup) */}
      <Button
        title="Show QR Code"
        onPress={() => dynamicClient.ui.mfa.qrCode.show()}
      />
      <Button
        title="Hide QR Code"
        onPress={() => dynamicClient.ui.mfa.qrCode.hide()}
      />

      {/* OTP Verification Screen */}
      <Button
        title="Show OTP Verification"
        onPress={() => dynamicClient.ui.mfa.otpVerification.show()}
      />
      <Button
        title="Hide OTP Verification"
        onPress={() => dynamicClient.ui.mfa.otpVerification.hide()}
      />

      {/* View Backup Codes Screen */}
      <Button
        title="Show Backup Codes"
        onPress={() => dynamicClient.ui.mfa.viewBackupCodes.show()}
      />
      <Button
        title="Hide Backup Codes"
        onPress={() => dynamicClient.ui.mfa.viewBackupCodes.hide()}
      />

      {/* Enter Backup Codes Screen */}
      <Button
        title="Show Enter Backup Codes"
        onPress={() => dynamicClient.ui.mfa.enterBackupCodes.show()}
      />
      <Button
        title="Hide Enter Backup Codes"
        onPress={() => dynamicClient.ui.mfa.enterBackupCodes.hide()}
      />
    </View>
  );
}
```
