Authentication
Passkeys
Implement phishing-resistant passwordless authentication using FIDO2/WebAuthn passkeys.
Passkeys
Passkeys are the most secure and user-friendly authentication method available. They use FIDO2/WebAuthn to provide phishing-resistant, passwordless sign-in with a 93% success rate (compared to 63% for passwords).
Why passkeys
| Metric | Passkeys | Passwords |
|---|---|---|
| Login success rate | 93% | 63% |
| Average sign-in time | 8.5 seconds | 31.2 seconds (with MFA) |
| Phishing resistant | Yes (origin-bound) | No |
| Help desk calls | 81% fewer | Baseline |
| Assurance level | AAL2 (single step) | AAL1 (needs MFA for AAL2) |
Passkeys satisfy AAL2 requirements in a single step because they verify both identity and device possession.
Types of passkeys
| Type | Storage | Sync | Use case |
|---|---|---|---|
| Synced passkeys | Cloud keychain (iCloud, Google, 1Password) | Cross-device | Primary authentication for most users |
| Device-bound passkeys | Hardware (YubiKey, Titan Key) | None | High-security environments (AAL3) |
Registering a passkey
Browser flow
import { usePasskeyEnrollment }
import { AvnologyId } from "@avnology/sdk-typescript";
const auth = new AvnologyId({
baseUrl: "https://api-id.avnology.net",
clientId: "your_client_id",
});
// User must be authenticated first
const session
import { usePasskeyEnrollment }
API flow (mobile apps)
Passkeys work in native mobile apps through API flows:
// React Native / Swift / Kotlin
const options = await auth.getPasskeyRegistrationOptions();
// Pass options to the platform's WebAuthn API
// iOS: ASAuthorizationPlatformPublicKeyCredentialProvider
// Android: Fido2ApiClient
const credential = await platformWebAuthn.create(options);
// Complete registration
Signing in with a passkey
TypeScript
React
curl
// One-step passwordless sign-in
const session = await auth.loginWithPasskey();
console.log("Signed in as:", session.identity.email);# Step 1: Get WebAuthn assertion options
curl -X POST https://api-id.avnology.net/v1/auth/passkey/login/begin \
-H "Content-Type: application/json"
# Step 2: Complete with the authenticator response
curl -X POST https://api-id.avnology.net/v1/auth/passkey/login/complete \
-H "Content-Type: application/json" \
-d '{
"id": "credential_id",
"rawId": "base64url_raw_id",
"response": {
"authenticatorData": "base64url_auth_data",
"clientDataJSON": "base64url_client_data",
"signature": "base64url_signature"
},
// One-step passwordless sign-in
const session = await auth.loginWithPasskey();
console.log("Signed in as:", session.identity.email);# Step 1: Get WebAuthn assertion options
curl -X POST https://api-id.avnology.net/v1/auth/passkey/login/begin \
-H "Content-Type: application/json"
# Step 2: Complete with the authenticator response
curl -X POST https://api-id.avnology.net/v1/auth/passkey/login/complete \
-H "Content-Type: application/json" \
-d '{
"id": "credential_id",
"rawId": "base64url_raw_id",
"response": {
"authenticatorData": "base64url_auth_data",
"clientDataJSON": "base64url_client_data",
"signature": "base64url_signature"
},
// One-step passwordless sign-in
const session = await auth.loginWithPasskey();
console.log("Signed in as:", session.identity.email);# Step 1: Get WebAuthn assertion options
curl -X POST https://api-id.avnology.net/v1/auth/passkey/login/begin \
-H "Content-Type: application/json"
# Step 2: Complete with the authenticator response
curl -X POST https://api-id.avnology.net/v1/auth/passkey/login/complete \
-H "Content-Type: application/json" \
-d '{
"id": "credential_id",
"rawId": "base64url_raw_id",
"response": {
"authenticatorData": "base64url_auth_data",
"clientDataJSON": "base64url_client_data",
"signature": "base64url_signature"
},
Managing passkeys
List registered passkeys
const passkeys = await auth.listPasskeys();
for (const passkey of passkeys) {
console.log(passkey.id);
console.log(passkey.displayName); // "My MacBook Pro"
console.log(passkey.createdAt);
Remove a passkey
await auth.removePasskey({ credentialId: "cred_abc123" });Conditional UI (autofill)
Passkeys support conditional UI, where the browser shows available passkeys in the username field's autofill dropdown:
// Check if conditional UI is available
if (await auth.isConditionalUIAvailable()) {
// Start conditional UI -- shows passkeys in autofill
const session = await auth.loginWithPasskey({ conditional: true });
}Browser and platform support
| Platform | Support |
|---|---|
| Chrome 108+ | Synced passkeys via Google Password Manager |
| Safari 16+ | Synced passkeys via iCloud Keychain |
| Firefox 122+ | Limited support |
| iOS 16+ | Native passkey support |
| Android 9+ | Synced passkeys via Google Password Manager |
| Windows 10+ | Windows Hello |
| 1Password, Dashlane, Bitwarden | Third-party passkey providers |
Security considerations
- Origin binding -- Passkeys are bound to the domain. A passkey for
id.avnology.netcannot be used onfake-avnology.com. - No shared secrets -- Unlike passwords, no secret is transmitted during authentication. The server only stores the public key.
- User presence required -- Each authentication requires a user gesture (biometric, PIN, or touch).
- Attestation -- In high-security environments, you can require attestation to verify the authenticator model.
Next steps
- Session management -- Session lifecycle
- Passwordless SMS -- SMS-based passwordless login
- Magic link -- Email-based passwordless login
- Mobile authentication -- Native app passkey flows