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.
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 for dashboard setup instructions.
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.
-
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.
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.
- 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.
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:
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>
);
}