> ## 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 for React Native
>
> While this SDK is still supported, we recommend using newer [JavaScript SDK](https://docs.dynamic.xyz/docs/javascript/reference/react-native-quickstart.md), which is optimized for React Native, but also comes with a host of other benefits.

## 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
>
> * **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] 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](https://docs.dynamic.xyz/docs/react-native/reference/setup-passkey.md).
>   * 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.

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


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