Install & Compatibility
Where this runs
tested against v? · npm install
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslnode 18–226 runs
build_error
glibcnode 18–226 runs
build_error
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
registerPasskey, authenticatePasskey, revokePasskey
✓ import { registerPasskey, authenticatePasskey, revokePasskey } from 'expo-passkey';
✗ const { registerPasskey } = require('expo-passkey');
Expo Passkey ships as an ES Module (ESM) and is TypeScript-ready. CommonJS `require` syntax will not work.
PasskeyStatus
✓ import { PasskeyStatus } from 'expo-passkey';
✗ import PasskeyStatus from 'expo-passkey';
A named export providing utilities like `isSupported()` for checking passkey availability. Do not use as a default import.
RegisterPasskeyOptions, AuthenticatePasskeyOptions
✓ import type { RegisterPasskeyOptions, AuthenticatePasskeyOptions } from 'expo-passkey';
✗ import { RegisterPasskeyOptions, AuthenticatePasskeyOptions } from 'expo-passkey';
These are TypeScript type definitions. Use `import type` to avoid bundling unnecessary runtime code, especially in environments that don't strip type imports.
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();
Errors
Common errors & fixes
401 Unauthorized error when attempting passkey authentication, even for unauthenticated users.
The backend's authentication challenge endpoint is incorrectly requiring an authenticated session before `v0.3.6`.
fixUpgrade `expo-passkey` to `v0.3.6` or later, and ensure your server-side endpoint for initiating passkey authentication does not enforce a prior user session.
Security vulnerabilities or 'Invalid userId' warnings reported for passkey registration/revocation.
Server-side code is accepting `userId` from client requests for sensitive operations, making it susceptible to manipulation (pre-`v0.3.0`).
fixUpgrade `expo-passkey` to `v0.3.0` or higher. On the backend, always obtain `userId` for passkey registration and revocation from the authenticated user's session, ignoring any `userId` sent from the client.
TypeError: revokePasskey is not a function or Argument of type '{ userId: string; credentialId: string; }' is not assignable to parameter of type '{ credentialId: string; }'.
Attempting to pass the `userId` parameter to `revokePasskey` after it was removed in `v0.3.0`.
fixRemove the `userId` parameter from `revokePasskey` calls in your client-side code. Ensure an authenticated session is established before calling `revokePasskey`.
Module not found: Can't resolve '@better-auth/expo' in '...' or similar peer dependency resolution errors.
A required peer dependency, such as `@better-auth/expo`, `expo-secure-store`, or `@simplewebauthn/server`, is either not installed or its version is incompatible.
fixInstall all specified peer dependencies manually using your package manager (e.g., `npm install @better-auth/expo expo-secure-store @simplewebauthn/server`). Check the `package.json` for exact version requirements and ensure compatibility.
Audit
Dependencies
exporequiredCore dependency for any Expo module.
reactrequiredFundamental dependency for React Native applications.
react-nativerequiredFundamental dependency for React Native applications.
@better-auth/exporequiredCore integration for the Better Auth backend services and client-side utilities.
better-authrequiredCore integration for the Better Auth backend services.
@simplewebauthn/serverrequiredWhile expo-passkey is client-side, a complete passkey solution requires a server-side component for challenge generation and verification, often leveraging this library. The package's ecosystem implies its necessity.
expo-secure-storerequiredUsed for securely storing sensitive data on the device, often relevant for authentication flows.
expo-local-authenticationrequiredEnables biometric authentication (Face ID, Touch ID, fingerprint) on native platforms.
zodrequiredUsed for schema validation, likely for API responses and internal data structures.