Expo Passkey

0.3.12 · active · verified Wed Apr 22

Expo Passkey is a cross-platform (iOS, Android, Web) module designed for Expo applications, providing comprehensive FIDO2/WebAuthn passkey authentication. It seamlessly integrates with the Better Auth ecosystem, offering an end-to-end solution for modern, frictionless biometric and platform authenticator-based login experiences. Currently at version 0.3.12, the package maintains an active development pace, demonstrated by recent critical security patches and breaking changes introduced in October 2025. Key differentiators include its unified passkey table structure that operates consistently across all supported platforms, seamless synchronization with iCloud Keychain and Google Password Manager for cross-device portability, and granular client-controlled WebAuthn security preferences, all built upon a robust framework emphasizing session-validated security for critical operations like registration and revocation.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates the client-side flow for registering and authenticating a passkey in an Expo application. It illustrates the typical three-step process: obtaining a challenge from a backend server, performing the client-side WebAuthn operation, and sending the result back to the server for verification, including considerations for web platform support.

import { registerPasskey, authenticatePasskey, PasskeyStatus } from 'expo-passkey';
import * as WebBrowser from 'expo-web-browser';
import { Platform } from 'react-native';

const API_BASE_URL = 'https://your-backend.com/api'; // Replace with your backend URL

async function handlePasskeyRegistration(userId: string) {
  try {
    console.log('Initiating passkey registration...');
    const response = await fetch(`${API_BASE_URL}/passkey/register/start`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId }), // Send userId to backend for challenge generation
    });
    const { challengeOptions } = await response.json();

    const registrationResult = await registerPasskey({
      challenge: challengeOptions,
      openWebBrowserAsync: Platform.OS === 'web' ? WebBrowser.openBrowserAsync : undefined,
    });

    const verificationResponse = await fetch(`${API_BASE_URL}/passkey/register/complete`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(registrationResult),
    });

    if (verificationResponse.ok) {
      console.log('Passkey registered successfully!');
      return true;
    } else {
      const error = await verificationResponse.json();
      console.error('Passkey registration failed on server:', error);
      return false;
    }
  } catch (error) {
    console.error('Error during passkey registration:', error);
    return false;
  }
}

async function handlePasskeyAuthentication() {
  try {
    console.log('Initiating passkey authentication...');
    const response = await fetch(`${API_BASE_URL}/passkey/authenticate/start`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      // No userId sent for auth challenge since v0.3.6
    });
    const { challengeOptions } = await response.json();

    const authenticationResult = await authenticatePasskey({
      challenge: challengeOptions,
      openWebBrowserAsync: Platform.OS === 'web' ? WebBrowser.openBrowserAsync : undefined,
    });

    const verificationResponse = await fetch(`${API_BASE_URL}/passkey/authenticate/complete`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(authenticationResult),
    });

    if (verificationResponse.ok) {
      const { user } = await verificationResponse.json(); // Backend might return user info
      console.log('Passkey authenticated successfully for user:', user?.id || 'unknown');
      return user;
    } else {
      const error = await verificationResponse.json();
      console.error('Passkey authentication failed on server:', error);
      return null;
    }
  } catch (error) {
    console.error('Error during passkey authentication:', error);
    return null;
  }
}

// Example of checking passkey support (e.g., in a useEffect hook)
// async function checkPasskeySupport() {
//   const supported = await PasskeyStatus.isSupported();
//   console.log('Passkey authentication supported:', supported);
// }
// checkPasskeySupport();

view raw JSON →