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

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 an 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://dynamic.xyz/docs/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://dynamic.xyz/docs/wallets/embedded-wallets/mpc/delegated-access/revoking-delegation) and the user next signs in.

## Using step-up authentication (recommended)

Use `requestedScopes` to receive [elevated access tokens](https://docs.dynamic.xyz/docs/auth/step-up-auth.md) instead of legacy MFA tokens. This is the recommended approach and will be required in the next major version.

> [tab] TOTP
>
> ```javascript theme={"system"}
> import {
>   isMfaRequiredForAction,
>   authenticateTotpMfaDevice,
>   MFAAction,
>   TokenScope,
> } from '@dynamic-labs-sdk/client';
> import { exportWaasPrivateKey } from '@dynamic-labs-sdk/client/waas';
>
> const onExportPrivateKeyClick = async () => {
>   const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
>
>   if (required) {
>     await authenticateTotpMfaDevice({
>       code: '123456',
>       requestedScopes: [TokenScope.Walletexport],
>     });
>   }
>
>   // Elevated token is now stored — the SDK attaches it automatically
>   await exportWaasPrivateKey(params);
> };
> ```

> [tab] Passkey
>
> ```javascript theme={"system"}
> import {
>   isMfaRequiredForAction,
>   authenticatePasskeyMFA,
>   MFAAction,
>   TokenScope,
> } from '@dynamic-labs-sdk/client';
> import { exportWaasPrivateKey } from '@dynamic-labs-sdk/client/waas';
>
> const onExportPrivateKeyClick = async () => {
>   const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
>
>   if (required) {
>     await authenticatePasskeyMFA({
>       requestedScopes: [TokenScope.Walletexport],
>     });
>   }
>
>   // Elevated token is now stored — the SDK attaches it automatically
>   await exportWaasPrivateKey(params);
> };
> ```

For a complete guide on step-up authentication including all verification methods, see [Step-Up Authentication](https://docs.dynamic.xyz/docs/javascript/authentication-methods/step-up-auth/overview.md).

## React

In React, use `useAuthenticateTotpMfaDevice` for the MFA challenge and call the one-shot `isMfaRequiredForAction` check directly inside your handler before the sensitive operation:

```tsx theme={"system"}
import { exportWaasPrivateKey } from '@dynamic-labs-sdk/client/waas';
import {
  isMfaRequiredForAction,
  MFAAction,
  TokenScope,
} from '@dynamic-labs-sdk/client';
import { useAuthenticateTotpMfaDevice } from '@dynamic-labs-sdk/react-hooks';
import { useState } from 'react';

function ExportKeyButton() {
  const [code, setCode] = useState('');
  const [needsMfa, setNeedsMfa] = useState(false);

  const { mutate: authenticateTotpMfaDevice } = useAuthenticateTotpMfaDevice();

  const handleExport = async () => {
    const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
    if (required) {
      setNeedsMfa(true);
      return;
    }
    await exportWaasPrivateKey(params);
  };

  const handleMfaSubmit = () => {
    authenticateTotpMfaDevice(
      { code, requestedScopes: [TokenScope.Walletexport] },
      {
        onSuccess: () => {
          setNeedsMfa(false);
          exportWaasPrivateKey(params);
        },
      }
    );
  };

  if (needsMfa) {
    return (
      <div>
        <input value={code} onChange={(e) => setCode(e.target.value)} placeholder="Authenticator code" />
        <button onClick={handleMfaSubmit}>Confirm</button>
      </div>
    );
  }

  return <button onClick={handleExport}>Export private key</button>;
}
```

## Using MFA tokens (deprecated)

> [!WARNING]
>
> `createMfaToken` and `createMfaTokenOptions` are deprecated and will be removed in the next major version. Use `requestedScopes` instead. See [migration guide](https://docs.dynamic.xyz/docs/auth/step-up-auth.md#migration-from-mfa-tokens).

> [tab] TOTP
>
> ```javascript theme={"system"}
> import { isMfaRequiredForAction, authenticateTotpMfaDevice, MFAAction } from '@dynamic-labs-sdk/client';
> import { exportWaasPrivateKey } from '@dynamic-labs-sdk/client/waas';
>
> const onExportPrivateKeyClick = async () => {
>   const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
>   if (required) {
>     await authenticateTotpMfaDevice({ code: '123456', createMfaTokenOptions: { singleUse: true } });
>   }
>   await exportWaasPrivateKey(params);
> };
> ```

> [tab] Passkey
>
> ```javascript theme={"system"}
> import { isMfaRequiredForAction, authenticatePasskeyMFA, MFAAction } from '@dynamic-labs-sdk/client';
> import { exportWaasPrivateKey } from '@dynamic-labs-sdk/client/waas';
>
> const onExportPrivateKeyClick = async () => {
>   const required = await isMfaRequiredForAction({ mfaAction: MFAAction.WalletWaasExport });
>   if (required) {
>     await authenticatePasskeyMFA({ createMfaToken: { singleUse: true } });
>   }
>   await exportWaasPrivateKey(params);
> };
> ```


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