React Native Passkey Integration
react-native-passkey provides native Passkey support for React Native applications targeting iOS 15.0+ and Android API 28+. It bridges WebAuthn API calls to the native platform credential managers, enabling secure, passwordless authentication. The package is currently at version 3.3.3 and maintains an active release cadence, with several minor and patch releases in the past year addressing fixes, updates, and new features like PRF extension support and improved error handling. A key differentiator is its direct native integration, abstracting away the platform-specific complexities of FIDO2 attestation and assertion, and offering support for advanced features like `largeBlob` and `authenticatorSelection` options. It ships with TypeScript types, facilitating safer development. The library works by taking FIDO2 attestation/assertion requests (typically from a backend) and handling the native UI and cryptographic operations, returning FIDO2 results for server verification.
Common errors
-
Error: Passkeys are not supported on this device.
cause The device's operating system version is below the minimum requirement (iOS 15.0+ or Android API 28+), or the device does not have hardware support for FIDO2/Passkeys.fixCheck `Passkey.isSupported()` before attempting Passkey operations. Inform the user if their device does not meet the requirements. Consider fallback authentication methods. -
Error: The given origin is not associated with this application.
cause The associated domain configuration (Apple App Site Association for iOS or Digital Asset Links for Android) is incorrect or missing on your web server or in your application's native project settings.fixVerify that your `apple-app-site-association` (iOS) or `assetlinks.json` (Android) file is correctly served at `/.well-known/` on your domain, and that its content matches your app's bundle ID/team ID (iOS) or package name/certificate SHA256 fingerprints (Android). For iOS, ensure the 'Associated Domains' capability is added in Xcode with `webcredentials:yourdomain.com`. -
Invariant Violation: `new NativeEventEmitter()` was called with a non-null argument without the native module.
cause The native module for react-native-passkey is not correctly linked or initialized. This can happen if `pod install` was not run for iOS, or if there are caching issues in React Native.fixFor iOS, navigate to your `ios` directory and run `pod install`. For both platforms, try clearing React Native caches: `npx react-native start --reset-cache`, and then rebuild your application: `npx react-native run-ios` or `npx react-native run-android`. -
Error: No matching credentials found.
cause During a Passkey assertion (login) request, the device could not find a previously registered Passkey that matches the criteria provided in the FIDO2 assertion request (e.g., `allowCredentials`).fixEnsure the `allowCredentials` array in your FIDO2 assertion request contains valid `id` values for Passkeys previously registered by the user. If the user hasn't registered a Passkey on that device, this is expected. Guide the user to register a Passkey first or use a fallback authentication method.
Warnings
- breaking Version 3.0.0 introduced significant restructuring of the iOS native code for improved maintainability. While specific breaking API changes are not explicitly listed beyond 'Support for more request options', this indicates a major internal overhaul that might affect custom native module integrations or debugging if not purely using the JS API.
- breaking The return type for `largeBlob.blob` was changed in v3.3.3 from a string-keyed dictionary to a plain array. This directly impacts how applications process the `largeBlob` extension data if they rely on specific dictionary keys.
- gotcha Incorrect error handling was fixed in v3.3.2, which previously returned generic errors instead of specific FIDO2 error types like 'UserCancelled' or 'RequestFailed'. This means older versions might not provide sufficient detail for user-facing error messages or retry logic.
- gotcha Both iOS and Android require specific associated domain configurations (Apple App Site Association and Digital Asset Links respectively) on your web server and within your app's native project settings. Failing to configure these correctly will prevent Passkey functionality from working.
- deprecated Early beta versions (e.g., 3.0.0-beta2) had issues with TypeScript type definitions and return types for `Passkey.create()` and `Passkey.get()`. While fixed in later RCs and stable releases, using very old beta versions might lead to type mismatches or runtime errors.
Install
-
npm install react-native-passkey -
yarn add react-native-passkey -
pnpm add react-native-passkey
Imports
- Passkey
const Passkey = require('react-native-passkey');import { Passkey } from 'react-native-passkey'; - PasskeyCreateResult
import { PasskeyCreateResult } from 'react-native-passkey';import type { PasskeyCreateResult } from 'react-native-passkey'; - PasskeyGetResult
import type { PasskeyGetResult } from 'react-native-passkey';
Quickstart
import { Passkey } from 'react-native-passkey';
import React, { useState, useEffect } from 'react';
import { View, Text, Button, Alert, ActivityIndicator, StyleSheet } from 'react-native';
const EXAMPLE_BACKEND_URL = process.env.PASSKEY_SERVER_URL ?? 'https://your-passkey-server.com';
const PasskeyDemo = () => {
const [isSupported, setIsSupported] = useState<boolean | null>(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsSupported(Passkey.isSupported());
}, []);
const handleCreatePasskey = async () => {
if (!isSupported) {
Alert.alert('Not Supported', 'Passkeys are not supported on this device.');
return;
}
setIsLoading(true);
try {
// This request would typically come from your backend
// For demo, we'll simulate a simple registration request
const registrationChallengeResponse = await fetch(`${EXAMPLE_BACKEND_URL}/register/challenge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'demoUser' + Date.now() })
});
const requestJson = await registrationChallengeResponse.json();
console.log('FIDO2 attestation request:', JSON.stringify(requestJson, null, 2));
const result = await Passkey.create(JSON.stringify(requestJson));
// Send the result to your server for verification
const verificationResponse = await fetch(`${EXAMPLE_BACKEND_URL}/register/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(result)
});
const verificationResult = await verificationResponse.json();
if (verificationResult.verified) {
Alert.alert('Success', 'Passkey created and verified!');
} else {
Alert.alert('Error', 'Passkey creation failed verification.');
}
} catch (error: any) {
console.error('Passkey creation error:', error);
Alert.alert('Error', `Failed to create Passkey: ${error.message}`);
} finally {
setIsLoading(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Passkey Demo</Text>
{isSupported === null ? (
<ActivityIndicator size="large" />
) : (
<Text style={styles.status}>Passkey Supported: {isSupported ? 'Yes' : 'No'}</Text>
)}
<Button
title="Create New Passkey"
onPress={handleCreatePasskey}
disabled={isLoading || !isSupported}
/>
{isLoading && <ActivityIndicator size="small" style={styles.spinner} />}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
status: {
fontSize: 18,
marginBottom: 20,
},
spinner: {
marginTop: 10
}
});
export default PasskeyDemo;