Skip to main content
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.

Dashboard Setup

  1. Go to the Security page.
  2. In the Action MFA section, enable your desired methods (TOTP and/or Passkeys).
  3. (Optional) Toggle “Require at onboarding” to force MFA setup during signup.
  4. Choose which events you want to protect with MFA and toggle them on.

Events that trigger Action-Based MFA

  • Waas Export - When exporting a private key on an MPC wallet.
  • Waas Refresh - When a wallet is delegated, 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 and the user next signs in.

Your UI SDK Implementation

  • Check requirement: Use useIsMfaRequiredForAction() for the specific action.
  • Check token: Use useGetMfaToken() if MFA is required.
  • Create token: If missing, collect OTP and call authenticateDevice({ code, createMfaToken }).
  • Perform action: Token is applied automatically after creation.
import { useMfa, useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
import { useGetMfaToken } from "@dynamic-labs/sdk-react-core";
import { MFAAction } from '@dynamic-labs/sdk-api-core';

export function EnsureTotpMfaToken() {
  const { authenticateDevice } = useMfa();
  const { getMfaToken } = useGetMfaToken();
  const isMfaRequiredForAction = useIsMfaRequiredForAction();

  const ensureToken = async (code?: string) => {
    const requires = await isMfaRequiredForAction({
      mfaAction: MFAAction.WalletWaasSign,
    });
    if (!requires) {
      // No MFA required for this action
      return;
    }

    const existing = await getMfaToken();
    if (existing) return existing;

    if (!code) throw new Error("OTP code required");
    const token = await authenticateDevice({
      code,
      createMfaToken: { singleUse: true } // or false for persistent
    });
    // Now perform the action that requires Action-Based MFA
    return token;
  };

  return null;
}
  • Only one verified TOTP device per user (device management applies across modes).
  • Recovery codes are single-use; regenerate with getRecoveryCodes(true) if exhausted.
Troubleshooting
  • Error: “MFA required” despite prompting — check useGetMfaToken() and attach token to backend requests.
  • Token unexpectedly missing — persistent tokens expire with session; create a new token when needed.

Dynamic UI Implementation

Note: The Dynamic UI is method-agnostic. It automatically prompts with whichever MFA method(s) you have enabled (TOTP and/or Passkeys). For action-based MFA, check if the specific action requires MFA and then open the Dynamic UI to create an MFA token.
import { usePromptMfaAuth, useIsMfaRequiredForAction } from "@dynamic-labs/sdk-react-core";
import { MFAAction } from '@dynamic-labs/sdk-api-core';

const isMfaRequiredForAction = useIsMfaRequiredForAction();
const promptMfaAuth = usePromptMfaAuth();

// Check if MFA is required for the action
const isMfaRequired = await isMfaRequiredForAction({
  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 promptMfaAuth({ createMfaToken: true }); // Create a single-use token for the action
}
// Perform the action
await primaryWallet.signMessage('Hello, world');