Passkey Authentication
Passkeys provide a secure, passwordless authentication method that uses public key cryptography. This guide covers how to implement passkey authentication in your React Native application using react-native-credentials-manager.
Understanding Passkeys
Passkeys are a replacement for passwords that provide stronger security and a better user experience. They use public key cryptography to authenticate users without the need to remember or type passwords.
Key advantages of passkeys include:
- Strong Security: Resistant to phishing, credential stuffing, and other common attacks
- Passwordless: No passwords to forget, reset, or have stolen
- Biometric Authentication: Uses device biometrics (fingerprint, face recognition) for verification
- Cross-Device Compatibility: Can be synced across devices via platform account (Google/Apple)
Integration Steps
1. Prerequisites
Ensure you've completed the installation and setup process, including:
- Digital Asset Links configuration for Android
- Associated Domains setup for iOS
- Domain verification
2. Passkey Registration (Sign Up)
To create a new passkey during user registration:
import { signUpWithPasskeys } from "react-native-credentials-manager";
// Create a registration request
const registrationRequest = {
challenge: "BASE64_ENCODED_CHALLENGE", // Random challenge from your server
rp: {
name: "Your App Name",
id: "yourdomain.com",
},
user: {
id: "BASE64_ENCODED_USER_ID", // User ID from your server
name: "username@example.com",
displayName: "User Name",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7, // ES256
},
{
type: "public-key",
alg: -257, // RS256
},
],
timeout: 60000, // 60 seconds
attestation: "none",
excludeCredentials: [], // Array of existing credentials to exclude
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
residentKey: "required",
userVerification: "required",
},
};
try {
const response = await signUpWithPasskeys(
registrationRequest,
false // preferImmediatelyAvailableCredentials (Android only)
);
// Send the response to your server for verification and storage
await sendToServer("/register-passkey", response);
console.log("Passkey registration successful");
} catch (error) {
console.error("Passkey registration failed:", error);
}
3. Passkey Authentication (Sign In)
To authenticate a user with an existing passkey:
import { signIn } from "react-native-credentials-manager";
// Create an authentication request
const authRequest = {
challenge: "BASE64_ENCODED_CHALLENGE", // Random challenge from your server
timeout: 60000, // 60 seconds
userVerification: "required",
rpId: "yourdomain.com",
};
try {
const credential = await signIn(["passkeys"], {
passkeys: authRequest,
});
if (credential.type === "passkey") {
// Send the authentication response to your server for verification
const authResult = await sendToServer(
"/verify-passkey",
credential.authenticationResponseJson
);
console.log("Passkey authentication successful");
}
} catch (error) {
console.error("Passkey authentication failed:", error);
}
Request Parameters Explained
Registration Request Parameters
Parameter | Type | Description | Required |
---|---|---|---|
challenge | string | Base64-encoded random challenge from your server | Yes |
rp.name | string | Human-readable name of your app/website | Yes |
rp.id | string | Domain name of your website (e.g., "example.com") | Yes |
user.id | string | Base64-encoded unique user identifier | Yes |
user.name | string | Username (often email address) | Yes |
user.displayName | string | Human-readable name for display | Yes |
pubKeyCredParams | array | Array of supported public key algorithms | Yes |
timeout | number | Timeout in milliseconds | Yes |
attestation | string | Attestation conveyance preference ("none", "indirect", "direct") | Yes |
excludeCredentials | array | Array of existing credentials to exclude | No |
authenticatorSelection | object | Authenticator selection criteria | No |
authenticatorSelection.authenticatorAttachment | string | Type of authenticator ("platform" or "cross-platform") | No |
authenticatorSelection.requireResidentKey | boolean | Whether to require resident key (discoverable credential) | No |
authenticatorSelection.residentKey | string | Resident key requirement ("discouraged", "preferred", "required") | No |
authenticatorSelection.userVerification | string | User verification requirement ("discouraged", "preferred", "required") | No |
Authentication Request Parameters
Parameter | Type | Description | Required |
---|---|---|---|
challenge | string | Base64-encoded random challenge from your server | Yes |
timeout | number | Timeout in milliseconds | Yes |
rpId | string | Domain name of your website (e.g., "example.com") | Yes |
userVerification | string | User verification requirement ("discouraged", "preferred", "required") | Yes |
allowCredentials | array | Array of credential descriptors to allow (optional) | No |
Platform-Specific Considerations
Android
- Android uses the Credential Manager API which supports passkeys on Android 14+ (API 34+)
- Passkey data is stored in the Android Keystore and synced via Google Password Manager
- The
preferImmediatelyAvailableCredentials
parameter is Android-specific
iOS
- iOS uses Authentication Services which supports passkeys on iOS 16.0+
- Passkey data is stored in the iOS Keychain and synced via iCloud Keychain
- The
preferImmediatelyAvailableCredentials
parameter is ignored on iOS
Best Practices
- Generate Strong Challenges: Use a cryptographically secure random number generator to create your challenges
- Verify on the Server: Always verify the authentication response on your server
- Use User Verification: Set
userVerification
to"required"
for enhanced security - Resident Keys: Use
residentKey: "required"
for a better user experience - Error Handling: Implement robust error handling for a smooth user experience
- User Education: Educate users about passkeys if they're unfamiliar with the concept
Example Helper Function
Here's a helper function to generate valid base64 challenges:
function generateChallenge(): string {
// Generate random bytes
const randomBytes = new Uint8Array(32);
crypto.getRandomValues(randomBytes);
// Convert to base64
return bytesToBase64(randomBytes);
}
function bytesToBase64(bytes: Uint8Array): string {
const binString = Array.from(bytes)
.map((byte) => String.fromCharCode(byte))
.join("");
return btoa(binString);
}
Server-Side Integration
Your server needs to:
- Generate and verify challenges
- Store public key credentials for each user
- Verify authentication responses
For server implementation examples and detailed guides, refer to:
- WebAuthn Guide
- SimpleWebAuthn Server Library (Node.js)
- Passkeys.dev (Cross-platform implementation examples)
Troubleshooting Passkey Issues
-
Registration Failures:
- Verify your Digital Asset Links/Associated Domains are correctly set up
- Ensure the
rp.id
matches your verified domain - Check that the challenge is properly encoded in base64
-
Authentication Failures:
- Ensure the user has previously registered a passkey
- Verify the
rpId
matches your verified domain - Check for proper base64 encoding of the challenge
-
Cross-Device Issues:
- Ensure the user's device is syncing credentials (Google/Apple account is set up)
- Verify that your implementation works with both resident and non-resident keys