Skip to main content
Passkeys MFA is still in close beta. Please contact us to request access to enable this feature for your app.

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 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
The user must acknowledge the recovery codes before the MFA setup flow is complete.

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
User does not need to acknowledge the recovery codes again in this flow.
React Native provides passkey MFA support through the dynamic client’s passkey methods.
React Native
import { dynamicClient } from '<path to client file>';
import { useState, useEffect } from 'react';

const MfaComponent = () => {
  const [passkeys, setPasskeys] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const loadPasskeys = async () => {
      try {
        setLoading(true);
        const userPasskeys = await dynamicClient.passkeys.get();
        setPasskeys(userPasskeys);
      } catch (error) {
        console.error('Failed to load passkeys:', error);
      } finally {
        setLoading(false);
      }
    };

    loadPasskeys();
  }, []);

  const handleRegisterPasskey = async () => {
    try {
      await dynamicClient.passkeys.register();
      // Refresh passkeys after registration
      const userPasskeys = await dynamicClient.passkeys.get();
      setPasskeys(userPasskeys);
    } catch (error) {
      console.error('Failed to register passkey:', error);
    }
  };

  const handleAuthenticateMFA = async () => {
    try {
      const verifyResponse = await dynamicClient.passkeys.authenticateMFA();
      console.log('MFA authentication successful:', verifyResponse);
    } catch (error) {
      console.error('MFA authentication failed:', error);
    }
  };

  const handleDeletePasskey = async (passkeyId: string) => {
    try {
      await dynamicClient.passkeys.delete({ passkeyId });
      // Refresh passkeys after deletion
      const userPasskeys = await dynamicClient.passkeys.get();
      setPasskeys(userPasskeys);
    } catch (error) {
      console.error('Failed to delete passkey:', error);
    }
  };

  if (loading) {
    return <Text>Loading passkeys...</Text>;
  }

  return (
    <View>
      <Text>Registered Passkeys: {passkeys.length}</Text>
      {passkeys.map((passkey) => (
        <View key={passkey.id}>
          <Text>Passkey ID: {passkey.id}</Text>
          <Button
            title="Delete"
            onPress={() => handleDeletePasskey(passkey.id)}
          />
        </View>
      ))}
      {passkeys.length > 0 ? (
        <Button
          title="Authenticate with passkey (MFA)"
          onPress={handleAuthenticateMFA}
        />
      ) : (
        <Text>No passkeys registered. Register a passkey first.</Text>
      )}
      <Button
        title="Register new passkey"
        onPress={handleRegisterPasskey}
      />
    </View>
  );
};
React Native requires platform setup for passkeys. See the setup guide: /react-native/setup-passkey