Skip to main content

Dashboard Setup

  1. Go to the Security page.
  2. In the Account MFA section, enable your desired methods (TOTP and/or Passkeys).
  3. (Optional) Toggle “Require at onboarding” to force MFA setup during signup.
  4. (Optional) Toggle “Session-based MFA” to require MFA for every new session.

Your UI SDK Implementation

  • 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.
    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.

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.
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 account-based MFA
      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:
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:
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>
  );
}