> ## Documentation Index
> Fetch the complete documentation index at: https://dynamic-docs-feat-sidebar-revamp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Headless MFA with TOTP device

<Warning>
  Headless MFA only supports one verified TOTP device.  Adding a second device will results in a 401
</Warning>

## Introduction

This guide will show you how to add TOTP MFA to your app using your own custom UI. You can also refer to the general MFA guide [here](/authentication-methods/mfa) to learn more about this feature (note that this guide is for account level MFA, rather than transaction level MFA).

For a working example, see: [https://codesandbox.io/p/sandbox/kh8g8s](https://codesandbox.io/p/sandbox/kh8g8s)

## General Flow

1. User logs in
2. User is redirected to the MFA view
3. User adds a device
4. User is redirected to the OTP view
5. User enters the OTP
6. User is redirected to the backup codes view
7. User acknowledges the backup codes

<Info>
  The user must accept the backup codes before the MFA flow is complete.
</Info>

## Step-by-Step Implementation

### Step 1: Import Necessary Modules and Components

Start by importing the required modules and components:

```typescript
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";

import { FC, useEffect, useState, ReactElement, useMemo, useRef } from "react";
import {
  DynamicContextProvider,
  DynamicWidget,
  useDynamicContext,
  useIsLoggedIn,
  useMfa,
  useSyncMfaFlow,
} from "@dynamic-labs/sdk-react-core";
import { MFADevice } from "@dynamic-labs/sdk-api-core";

import QRCodeUtil from "qrcode";
```

### Step 2: Define Types

Define the types for the MFA registration data.

```typescript
type MfaRegisterData = {
  uri: string;
  secret: string;
};
```

### Step 3: Create the `MfaView` Component

This component handles the main logic for MFA, including device management, QR code generation, OTP submission, and backup codes.

```typescript
export const MfaView = () => {
  const [userDevices, setUserDevices] = useState<MFADevice[]>([]);
  const [mfaRegisterData, setMfaRegisterData] = useState<MfaRegisterData>();
  const [currentView, setCurrentView] = useState<string>("devices");
  const [backupCodes, setBackupCodes] = useState<string[]>([]);
  const [error, setError] = useState<string>();

  const isLogged = useIsLoggedIn();
  const {
    addDevice,
    authenticateDevice,
    getUserDevices,
    getRecoveryCodes,
    completeAcknowledgement,
  } = useMfa();

  const refreshUserDevices = async () => {
    const devices = await getUserDevices();
    setUserDevices(devices);
  };

  const { userWithMissingInfo, handleLogOut } = useDynamicContext();
  useEffect(() => {
    if (isLogged) {
      refreshUserDevices();
    }
  }, [isLogged]);

  useSyncMfaFlow({
    handler: async () => {
      if (userWithMissingInfo?.scope?.includes("requiresAdditionalAuth")) {
        getUserDevices().then(async (devices) => {
          if (devices.length === 0) {
            setError(undefined);
            const { uri, secret } = await addDevice();
            setMfaRegisterData({ secret, uri });
            setCurrentView("qr-code");
          } else {
            setError(undefined);
            setMfaRegisterData(undefined);
            setCurrentView("otp");
          }
        });
      } else {
        getRecoveryCodes().then(setBackupCodes);
        setCurrentView("backup-codes");
      }
    },
  });

  const onAddDevice = async () => {
    setError(undefined);
    const { uri, secret } = await addDevice();
    setMfaRegisterData({ secret, uri });
    setCurrentView("qr-code");
  };

  const onQRCodeContinue = async () => {
    setError(undefined);
    setMfaRegisterData(undefined);
    setCurrentView("otp");
  };

  const onOtpSubmit = async (code: string) => {
    try {
      await authenticateDevice({ code });
      getRecoveryCodes().then(setBackupCodes);
      setCurrentView("backup-codes");
      refreshUserDevices();
    } catch (e) {
      setError(e.message);
    }
  };

  return (
    <div className="headless-mfa">
      <DynamicWidget />
      <button onClick={handleLogOut}>log out</button>
      {error && <div className="headless-mfa__section error">{error}</div>}
      {currentView === "devices" && (
        <div className="headless-mfa__section">
          <p>
            <b>Devices</b>
          </p>
          <pre>{JSON.stringify(userDevices, null, 2)}</pre>
          <button onClick={() => onAddDevice()}>Add Device</button>
        </div>
      )}
      {currentView === "qr-code" && mfaRegisterData && (
        <QRCodeView data={mfaRegisterData} onContinue={onQRCodeContinue} />
      )}
      {currentView === "otp" && <OTPView onSubmit={onOtpSubmit} />}
      {currentView === "backup-codes" && (
        <BackupCodesView
          codes={backupCodes}
          onAccept={completeAcknowledgement}
        />
      )}
      <button
        onClick={async () => {
          const codes = await getRecoveryCodes(true);
          setBackupCodes(codes);
          setCurrentView("backup-codes");
        }}
      >
        Generate Recovery Codes
      </button>
    </div>
  );
};
```

#### Deleting an MFA device

```typescript
const { authenticateDevice, deleteUserDevice } = useMfa();

...

const deleteDevice = async (deviceId: string, code: string) => {
  const mfaAuthToken = await authenticateDevice({ code, deviceId });
  await deleteUserDevice(deviceId, mfaAuthToken);
}
```

**Explanation:**

* **State Management:**

  * `userDevices`: Stores the list of MFA devices associated with the user.
  * `mfaRegisterData`: Stores the data required for MFA registration (QR code URI and secret).
  * `currentView`: Tracks the current view (e.g., 'devices', 'qr-code', 'otp', 'backup-codes').
  * `backupCodes`: Stores the backup codes for MFA.
  * `error`: Stores any error messages.

* **Hooks:**

  * `useIsLoggedIn`: Checks if the user is logged in.
  * `useMfa`: Provides functions for managing MFA (e.g., `addDevice`, `authenticateDevice`, `getUserDevices`, `deleteUserDevice`, `getRecoveryCodes`, `completeAcknowledgement`).
  * `useDynamicContext`: Provides user context and logout function.
  * `useEffect`: Refreshes user devices when the user logs in.
  * `useSyncMfaFlow`: Synchronizes the MFA flow based on the user's authentication status.

* **Functions:**

  * `refreshUserDevices`: Fetches and sets the user's MFA devices.
  * `onAddDevice`: Initiates the process of adding a new MFA device.
  * `onQRCodeContinue`: Proceeds to the OTP view after displaying the QR code.
  * `onOtpSubmit`: Authenticates the device using the provided OTP and fetches backup codes.

* **Return Statement:**
  * Renders the UI components based on the current view and state.

### Step 4: Create Supporting Components

Define the supporting components for displaying backup codes, QR code, and OTP input.

```typescript
const BackupCodesView = ({
  codes,
  onAccept,
}: {
  codes: string[];
  onAccept: () => void;
}) => (
  <>
    <p>Backup Codes</p>
    {codes.map((code) => (
      <p key={code}>{code}</p>
    ))}
    <button onClick={onAccept}>Accept</button>
  </>
);

const LogIn = () => (
  <>
    <p>User not logged in!</p>
    <DynamicWidget />
  </>
);

const QRCodeView = ({
  data,
  onContinue,
}: {
  data: MfaRegisterData;
  onContinue: () => void;
}) => {
  const canvasRef = useRef(null);

  useEffect(() => {
    if (!canvasRef.current) {
      return;
    }
    QRCodeUtil.toCanvas(canvasRef.current, data.uri, function (error: any) {
      if (error) console.error(error);
      console.log("success!");
    });
  });

  return (
    <>
      <div style={{ width: "320px", height: "320px" }}>
        <canvas id="canvas" ref={canvasRef}></canvas>
      </div>
      <p>Secret: {data.secret}</p>
      <button onClick={onContinue}>Continue</button>
    </>
  );
};

const OTPView = ({ onSubmit }: { onSubmit: (code: string) => void }) => (
  <form
    key="sms-form"
    onSubmit={(e) => {
      e.preventDefault();
      onSubmit(e.currentTarget.otp.value);
    }}
    className="headless-mfa__form"
  >
    <div className="headless-mfa__form__section">
      <label htmlFor="otp">OTP:</label>
      <input type="text" name="otp" placeholder="123456" />
    </div>
    <button type="submit">Submit</button>
  </form>
);
```

**Explanation:**

* **BackupCodesView:**

  * Displays the backup codes and provides an "Accept" button to acknowledge them.

* **LogIn:**

  * Displays a message indicating that the user is not logged in and shows the `DynamicWidget` for login.

* **QRCodeView:**

  * Displays the QR code for MFA setup and the secret key. Provides a "Continue" button to proceed to the OTP view.

* **OTPView:**
  * Provides a form for the user to enter the OTP. On form submission, it calls the `onSubmit` function with the entered OTP.

### Step 5: Create the Main `HeadlessMfaView` Component

This component checks if the user is logged in and displays the appropriate view.

```typescript
export const HeadlessMfaView: FC = () => {
  const { user, userWithMissingInfo } = useDynamicContext();

  return (
    <div className="headless-mfa">
      {user || userWithMissingInfo ? <MfaView /> : <LogIn />}
    </div>
  );
};

export default function App() {
  return (
    <div className="App">
      <DynamicContextProvider
        settings={{
          environmentId: "YOUR_ENV_ID",
          walletConnectors: [EthereumWalletConnectors],
        }}
      >
        <HeadlessMfaView />
      </DynamicContextProvider>
    </div>
  );
}
```

**Explanation:**

* **HeadlessMfaView:**

  * Checks if the user is logged in or has missing information that requires additional authentication.
  * If the user is logged in, it renders the `MfaView` component.
  * If the user is not logged in, it renders the `LogIn` component.

* **App:**
  * Your main app entry point.
  * Make sure to set you environment id.

### Conclusion

By following this detailed breakdown, you should have a clear understanding of how the provided code implements a headless MFA solution using a custom UI. Each part of the code is explained, including the purpose of each component, hook, and function. This should help you understand the overall flow and how to customize it for your needs.
