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

# Action-Based MFA

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

Action-Based MFA requires users to verify their identity for sensitive actions like transactions.
By default, we only require action-based MFA once the user already has a MFA method registered.

## Console Setup

Configure Action-Based MFA in the Dynamic Console before implementing in your application. See [End-User MFA Configuration — Action-Based MFA](https://docs.dynamic.xyz/docs/platform/console/end-user-mfa.md#action-based-mfa) for console setup instructions.

## Events that trigger Action-Based MFA

* Waas Export — When exporting a private key on an MPC wallet.
* Waas Refresh — When a wallet is [delegated](https://docs.dynamic.xyz/docs/react-native/wallets/embedded-wallets/mpc/delegated-access), or when a user claims a pregenerated MPC wallet for the first time.
* WaaS Sign — When any signature is performed i.e. a message, a transaction, typed data, authorization, etc.
* WaaS Reshare — When a wallet is approved or revoked from [delegated access](https://docs.dynamic.xyz/docs/react-native/wallets/embedded-wallets/mpc/delegated-access/revoking-delegation.md) and the user next signs in.

## Your UI SDK Implementation

> [tab] TOTP
>
> * **Check requirement**: Use `client.mfa.isRequiredForAction()` for the specific action.
>
>   * **Create token**: If required, collect OTP and call `client.mfa.authenticateDevice({ code, createMfaToken })`.
>   * **Perform action**: Token is applied automatically after creation.
>
>   ```typescript theme={"system"}
>   import React, { useState } from 'react';
>   import { Alert, View, TextInput, Button } from 'react-native';
>   import { MFAAction } from '@dynamic-labs/sdk-api-core';
>   import { useDynamic } from './path-to-your-client';
>
>   export function EnsureTotpMfaToken() {
>     const client = useDynamic();
>     const [code, setCode] = useState('');
>     const [loading, setLoading] = useState(false);
>
>     const promptForMfaIfRequired = async () => {
>       try {
>         setLoading(true);
>
>         // Check if MFA is required for this action
>         const required = await client.mfa.isRequiredForAction({
>           mfaAction: MFAAction.WalletWaasSign,
>         });
>
>         if (!required) {
>           // No MFA required, proceed with action
>           Alert.alert('Success', 'No MFA required for this action');
>           return;
>         }
>
>         if (!code.trim()) {
>           Alert.alert('Error', 'Please enter your 6-digit code');
>           return;
>         }
>
>         // Create MFA token with the TOTP code
>         await client.mfa.authenticateDevice({
>           code,
>           createMfaToken: { singleUse: true },
>         });
>
>         // Now perform the action that requires Action-Based MFA
>         // The token is automatically included in subsequent requests
>         Alert.alert('Success', 'MFA token created, proceed with action');
>       } catch (error) {
>         console.error('Failed to authenticate with MFA:', error);
>         Alert.alert('Error', 'Failed to authenticate with MFA');
>       } finally {
>         setLoading(false);
>       }
>     };
>
>     return (
>       <View>
>         <TextInput
>           placeholder="Enter 6-digit code"
>           value={code}
>           onChangeText={setCode}
>           keyboardType="numeric"
>           maxLength={6}
>         />
>         <Button
>           title="Authenticate & Proceed"
>           onPress={performSensitiveAction}
>           disabled={loading}
>         />
>       </View>
>     );
>   }
>   ```
>
>   * Only one verified TOTP device per user (device management applies across modes).
>   * Recovery codes are single-use; regenerate with `client.mfa.getNewRecoveryCodes()` if exhausted.
>
>   **Troubleshooting**
>
>   * Error: "MFA required" despite prompting — ensure you authenticate before performing the action.
>   * Token unexpectedly missing — tokens expire with session; create a new token when needed.

> [tab] Passkey
>
> * **Check requirement**: Use `client.mfa.isRequiredForAction()` for the specific action.
>
>   * **Check token**: Use `client.mfa.getMfaToken()` if MFA is required.
>   * **Register if needed**: If the user has no passkeys (common when "Require at onboarding" is off), call `client.passkeys.register()` first.
>   * **Create token**: Call `client.passkeys.authenticateMFA({ createMfaToken })` if missing.
>   * **Perform action**: Token is applied automatically after creation.
>
>   ```typescript theme={"system"}
>   import React, { useState } from 'react';
>   import { Alert, View, Button } from 'react-native';
>   import { MFAAction } from '@dynamic-labs/sdk-api-core';
>   import { useDynamic } from './path-to-your-client';
>
>   export function EnsurePasskeyMfaToken() {
>     const client = useDynamic();
>     const [loading, setLoading] = useState(false);
>
>     const promptForMfaIfRequired = async () => {
>       try {
>         setLoading(true);
>
>         // Check if MFA is required for this action
>         const requires = await client.mfa.isRequiredForAction({
>           mfaAction: MFAAction.WalletWaasSign,
>         });
>
>         if (!requires) {
>           // No MFA required for this action
>           return;
>         }
>
>         // Important: if you don't have MFA required at onboarding and you want to force the user
>         // to setup MFA prior to performing the action, you should skip any checks for MFA requirement above (isMfaRequiredForAction)
>         // and force a registration or authentication of the MFA method.
>
>         // If the user doesn't have a passkey yet, register one first
>         const passkeys = await client.passkeys.get();
>         if (passkeys.length === 0) {
>           await client.passkeys.register();
>         }
>
>         // Create MFA token using passkey
>         await client.passkeys.authenticateMFA({
>           createMfaToken: { singleUse: false }, // or true for one-time
>         });
>
>         // Now perform the action that requires Action-Based MFA
>       } catch (error) {
>         console.error('Failed to create MFA token:', error);
>         Alert.alert('Error', 'Failed to authenticate with passkey');
>         throw error;
>       } finally {
>         setLoading(false);
>       }
>     };
>
>     return (
>       <View>
>         <Button
>           title="Authenticate & Proceed"
>           onPress={ensureToken}
>           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).
>   * Consider persistent tokens for multiple sensitive actions in-session.
>
>   **Troubleshooting**
>
>   * Error: "MFA required" when performing actions — ensure you check `getMfaToken()` before creating a new token and include it in the request.
>   * Error: "Passkey not supported" — verify platform setup and passkey support.

## Dynamic UI Implementation

The React Native SDK provides a UI method to prompt MFA authentication. Use `client.mfa.isRequiredForAction()` to check if MFA is required, then use `client.ui.mfa.show()` to prompt the user.

Note: The Dynamic UI is method-agnostic. It automatically prompts with whichever MFA method(s) you have enabled (TOTP only for React Native).

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

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

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

      // Check if MFA is required for the action
      const isMfaRequired = await client.mfa.isRequiredForAction({
        mfaAction: MFAAction.WalletWaasSign, // Specify action configured for Action-Based MFA
      });

      if (isMfaRequired) {
        // Opens the Dynamic UI to prompt the user to authenticate with MFA
        await client.ui.mfa.show({ createMfaToken: true }); // Create a single-use token for the action
      }

      // Perform the action
      // await primaryWallet.signMessage('Hello, world');
      Alert.alert('Success', 'Action completed successfully');
    } catch (error) {
      console.error('Failed to perform action:', error);
      Alert.alert('Error', 'Failed to perform action');
    } finally {
      setLoading(false);
    }
  };

  return (
    <View>
      <Button
        title="Perform Sensitive Action"
        onPress={performAction}
        disabled={loading}
      />
    </View>
  );
}
```

**Note**: For Action-Based MFA in React Native, only TOTP authentication is currently supported. Passkey MFA is not available in React Native.


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