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

<Warning>
  Passkeys MFA is still in close beta. Please contact us to request access to enable this feature for your app.
</Warning>

## Introduction

This guide will show you how to add a passkey for 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).

## General Flow

### When user already has a passkey registered

1. User logs in
2. User is redirected to the MFA view
3. User adds a passkey
4. User is redirected to the recovery codes view
5. User acknowledges the recovery codes

<Info>
  The user must acknowledge the recovery codes before the MFA setup flow is complete.
</Info>

### When user does not have a passkey registered

1. User logs in
2. User is redirected to the MFA view
3. User selects existing passkey to authenticate

<Info>
  User does not need to acknowledge the recovery codes again in this flow.
</Info>

## Implementation 1: using our useSyncMfaFlow hook

We provide a hook that will use a provided handler to automatically trigger the MFA flow when required.

```typescript
import {
  useDynamicContext,
  useSyncMfaFlow,
  useRegisterPasskey,
  useAuthenticatePasskeyMFA,
  useMfa,
  useGetPasskeys,
} from "@dynamic-labs/sdk-react-core";

export const MfaComponent = () => {
  const { userWithMissingInfo } = useDynamicContext();

  const { getRecoveryCodes } = useMfa();
  const getPasskeys = useGetPasskeys();
  const registerPasskey = useRegisterPasskey();
  const authenticatePasskeyMFA = useAuthenticatePasskeyMFA();

  useSyncMfaFlow({
    // this handler will run in one of these three scenarios:
    // 1. when user is required to register a passkey on login
    // 2. when user has a registered passkey and is required to authenticate after login
    // 3. when user is still pending recovery codes acknowledgement
    handler: async () => {
      // this checks if the user is pending registration or authentication of a passkey
      if (userWithMissingInfo?.scope?.includes("requiresAdditionalAuth")) {
        getPasskeys().then(async (passkeys) => {
          // if the user has no passkeys, we need to register a new one
          if (passkeys.length === 0) {
            // here you can show a view to display while the user is prompted to register a passkey
            // e.g. setView('passkey-registration')

            await registerPasskey();
          } else {
            // if the user has a passkey, we need to authenticate it

            // here you can show a view to display while the user is prompted to authenticate their passkey
            // e.g. setView('passkey-authentication')

            await authenticatePasskeyMFA({
              createMfaToken: {
                singleUse: true,
              },
            });
          }
        });
      } else {
        // if the user already has a passkey registered and authenticated,
        // but is pending recovery codes acknowledgement

        const recoveryCodes = await getRecoveryCodes();

        // here you can show a view to display the recovery codes with a button to acknowledge them
        // to acknowledge the recovery codes, you can call the `completeAcknowledgement()` function
        // e.g. setView('recovery-codes', { recoveryCodes, onAck: completeAcknowledgement })
      }
    },
  });
};
```

## Implementation 2: you decide when to trigger each step of the MFA flow

<Warning>
  If you set MFA as required for your environment, the user won't be considered fully authenticated until they have completed the entire MFA flow.
</Warning>

```typescript
import { UserPasskey } from "@dynamic-labs/sdk-api-core";
import {
  useAuthenticatePasskeyMFA,
  useGetPasskeys,
  useIsLoggedIn,
  useMfa,
  useRegisterPasskey,
} from "@dynamic-labs/sdk-react-core";
import { useCallback, useEffect, useState } from "react";

export const MfaComponent = () => {
  // when the user is pending any MFA action, Dynamic will set `userWithMissingInfo`
  // other wise it will be undefined and `user` will be defined instead
  const isLogged = useIsLoggedIn();

  const { getRecoveryCodes, completeAcknowledgement } = useMfa();
  const getPasskeys = useGetPasskeys();
  const registerPasskey = useRegisterPasskey();
  const authenticatePasskeyMFA = useAuthenticatePasskeyMFA();

  const [error, setError] = useState<string>();

  const [userPasskeys, setUserPasskeys] = useState<UserPasskey[]>([]);
  const [recoveryCodes, setRecoveryCodes] = useState<string[]>([]);

  // refresh the user's passkeys
  const refreshUserPasskeys = useCallback(async () => {
    const passkeys = await getPasskeys();
    setUserPasskeys(passkeys);
  }, [getPasskeys]);

  // fetch passkeys when the user is logged in
  // so you can display them in your UI
  useEffect(() => {
    if (isLogged) {
      refreshUserPasskeys();
    }
  }, [isLogged, refreshUserPasskeys]);

  // register a new passkey
  const handleRegisterPasskey = async () => {
    // here you can show a view to display while the user is prompted to register a passkey
    // e.g. setView('passkey-registration')

    try {
      await registerPasskey();

      // if you want to refresh the user's passkeys after registering a new one
      refreshUserPasskeys();
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  };

  const handleAuthenticatePasskey = async () => {
    // here you can show a view to display while the user is prompted to authenticate their passkey
    // e.g. setView('passkey-authentication')

    try {
      // if you pass in a createMfaToken option as shown below,
      // `authenticatePasskeyMFA` will return a single use MFA token
      const mfaAuthToken = await authenticatePasskeyMFA({
        createMfaToken: {
          singleUse: true,
        },
      });

      console.log("mfaAuthToken", mfaAuthToken);
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  };

  const handleRecoveryCodes = async () => {
    try {
      const recoveryCodes = await getRecoveryCodes();

      // here you can show a view to display the recovery codes with a button to acknowledge them
      // to acknowledge the recovery codes, you can call the `completeAcknowledgement()` function
      // e.g. setView('recovery-codes', { recoveryCodes, onAck: completeAcknowledgement })
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
    }
  };

  return (
    <div className="headless-mfa-view">
      <div className="headless-mfa-view__section">
        <p>
          <b>Passkeys</b>
        </p>
        {userPasskeys.map((passkey) => (
          <div key={passkey.id}>
            <p>Id: {passkey.id}</p>
            <p>Alias: {passkey.alias}</p>
            <p>Credential Id: {passkey.credentialId}</p>
            <p>Origin: {passkey.origin}</p>
            <p>Storage: {passkey.storage?.name}</p>
            <p>Created At: {new Date(passkey.createdAt).toLocaleString()}</p>
            <p>
              Updated At:{" "}
              {passkey.updatedAt
                ? new Date(passkey.updatedAt).toLocaleString()
                : "N/A"}
            </p>
          </div>
        ))}
      </div>

      {error && <div className="headless-mfa__section error">{error}</div>}
      {recoveryCodes && (
        <div className="headless-mfa-view__section">
          <p className="headless-mfa-view__codes">
            Recovery Codes: {recoveryCodes.join(", ")}
          </p>
        </div>
      )}

      <div className="headless-mfa-view__section">
        <button onClick={() => handleRegisterPasskey()}>
          Register Passkey
        </button>
      </div>
      <div className="headless-mfa-view__section">
        <button onClick={() => handleAuthenticatePasskey()}>
          Authenticate Passkey
        </button>
      </div>
      <div className="headless-mfa-view__section">
        <button onClick={() => handleRecoveryCodes()}>
          Generate Recovery Codes
        </button>
      </div>
    </div>
  );
};
```
