This guide covers building a complete Passkey MFA flow with a custom UI. This includes registering a passkey, authenticating with it, and handling recovery codes.
Passkeys MFA is currently in closed beta. Contact us to request access.

1. Setup and Hooks

To begin, import the necessary hooks from @dynamic-labs/sdk-react-core and set up your component’s state. You’ll need state for managing passkeys, recovery codes, and any potential errors. The primary hooks used are useRegisterPasskey to add a new passkey and useAuthenticatePasskeyMFA to verify one.

2. Fetching User Passkeys

When the component mounts, use the useGetPasskeys hook to fetch and display any passkeys the user has already registered. This is typically done inside a useEffect hook that runs when the user is logged in.

3. Handling the MFA Flow

The useSyncMfaFlow hook is essential for account-based MFA. It automatically detects if an MFA challenge is required for the current user. If it is, the handler function is called, allowing you to trigger the appropriate action—either registering a new passkey if none exist, or authenticating an existing one.

4. Registering and Authenticating

Create handler functions that call the registerPasskey and authenticatePasskeyMFA functions from their respective hooks. These functions will trigger the browser’s native passkey UI. For action-based MFA, you can pass a createMfaToken option to authenticatePasskeyMFA to generate a token for authorizing sensitive actions.

5. Building the UI

Your UI should provide buttons to register and authenticate passkeys. It should also display a list of registered passkeys and show recovery codes to the user after a successful MFA setup.

Advanced: Token & Recovery Management

Token Management (for Action-Based MFA): When using Passkeys for action-based MFA, you need to manage the MFA token.
  • Check for existing token: Before prompting for MFA, check if a valid token already exists for the user’s session using useGetMfaToken.
  • Create single-use vs. persistent tokens: You can create a token that’s valid for a single action (singleUse: true) or one that persists for the user’s session (singleUse: false).
// Create a persistent token
const mfaAuthToken = await authenticatePasskeyMFA({
  createMfaToken: { singleUse: false },
});
Custom Recovery Flows: You can force the generation of new recovery codes, which will invalidate any previously issued codes.
const { getRecoveryCodes } = useMfa();

const generateNewCodes = async () => {
  const codes = await getRecoveryCodes(true); // force new codes
  setRecoveryCodes(codes);
};

Troubleshooting

  • Error: “Passkey not supported”: Check browser compatibility. Passkeys require WebAuthn support and are only enabled on secure contexts (HTTPS) in production.
  • Error: “MFA required” when performing actions: Ensure you are using getMfaToken() to check for an existing token before attempting to create a new one.
  • Recovery codes not working: Each recovery code is single-use. Generate new codes if all have been consumed.