FIDO2 vs Passkeys: A Technical Guide
FIDO2 and passkeys get used as if they were the same thing. They are not. FIDO2 is a protocol family; a passkey is a product concept built on one specific WebAuthn feature. This guide explains what each actually is, where they overlap, and how to decide what to deploy, from the relying party's perspective.
What FIDO2 actually is
FIDO2 is the FIDO Alliance's umbrella for the modern passwordless authentication stack. It is two specifications, not a device:
- WebAuthn: the W3C specification for the browser-to-server API (
navigator.credentials.create()andget()). It defines the challenge flow, origin binding, attestation, and assertion verification. - CTAP2: the FIDO Alliance specification for the client-to-authenticator protocol (USB, NFC, BLE, and the internal platform transport). This is what YubiKey-class devices speak.
Its predecessor, U2F (CTAP1), supported only non-discoverable credentials. FIDO2 added discoverable credentials, platform authenticators, and user verification.
A security key is a FIDO2 authenticator. The TPM-backed platform authenticator in a laptop is also a FIDO2 authenticator, and so, technically, is the keychain that produces a passkey. FIDO2 is the protocol layer underneath all of them.
What passkeys actually are
"Passkey" is a product term, coined by Apple at WWDC 2022 and since adopted by Google, Microsoft, and the FIDO Alliance itself. Under the hood, a passkey is a WebAuthn credential with two specific properties:
- Discoverable: a resident key. The authenticator stores the credential and can find it for a given relying party without the server sending credential IDs. This is what makes "no username" sign-in flows possible.
- Usually synced: the platform provider backs the credential up (iCloud Keychain, Google Password Manager, 1Password, Dashlane, Bitwarden, and others), giving users a recovery path if they lose a device.
The practical consequence: every passkey is a FIDO2 credential, but not every FIDO2 credential is a passkey. A security key with attestation and no sync is FIDO2, but nobody calls it a passkey.
The technical differences
| Property | Security key (CTAP2) | Passkey |
|---|---|---|
| Discoverability | Optional: server sends credential IDs | Required: resident key |
| Key storage | Device-bound; private key never leaves hardware | Synced by provider; backup-eligible |
| Attestation | packed / tpm / android-key; stable AAGUID | Often "none"; AAGUID less meaningful |
| User verification | Configurable: UV (PIN/biometric) or presence-only | Expected: biometric or PIN |
| Roaming | Physical device: plug or tap | Cross-device via QR + BLE (hybrid transport) |
| signCount | Increments, usable for clone detection | Often 0 on platform authenticators |
| Loss / recovery | Register multiple keys; lost key = lost access | Provider sync + account recovery |
| Enterprise control | AAGUID allowlists, attestation policies | Limited; attestation typically unavailable |
Discoverability
This is the defining difference. A classic U2F-style security key stores credentials with the server: the browser sends a list of credential IDs and the key picks one. A passkey is a resident key: the authenticator holds the credential and can be asked "which of your stored credentials belong to this origin?" without any IDs. That enables the username-less, device-agnostic UX passkeys are known for.
Attestation and AAGUID
Security keys ship a stable AAGUID and support attestation formats (packed, TPM, Android Key), which lets a relying party prove which model of authenticator was used, the basis of enterprise AAGUID allowlists. Synced passkeys typically return attestation: "none", because the credential is platform- and provider-dependent. If your policy requires hardware attestation, passkeys will not satisfy it.
Sync and backup eligibility
Since WebAuthn Level 2, assertions carry backup-eligibility flags: be (backup eligible) and bs (backup state). A synced passkey reports bs: true: the key material may exist outside the original authenticator. A device-bound security key reports bs: false. Relying parties that need strict device-binding can read these flags and reject credentials that do not meet the policy.
User verification
User verification (UV) means the authenticator proved the user is present via biometric or PIN. User presence (UP) is just a touch. Security keys let you choose per registration ( userVerification: "required" | "preferred" | "discouraged" ). Passkeys are designed around UV: a synced passkey without a biometric or PIN is not much better than a password.
What they share
1. Relying party
Sends a fresh random challenge
2. Browser
Binds the request to the page origin
3. Authenticator
Verifies the user and signs challenge + origin
4. Relying party
Verifies challenge, origin, signature and flags
Both are phishing-resistant by construction: origin-bound key pairs, no shared secrets, challenge-response authentication. Credential stuffing and classic phishing fail against both, because a credential minted for https://bank.example is useless against a lookalike origin.
What neither protects against: the session after authentication. A reverse-proxy phishing kit (AiTM-style) still rides the session cookie once the user has signed in. Passkeys move the credential layer forward, but they do not make session-layer defenses obsolete. That is exactly the MITM attack surface we spend our time on, and it is worth keeping in your threat model. Nor does authentication authorize anything: a passkey-authenticated user can still read other users' data through an IDOR.
How to choose
Consumer-facing apps: passkeys
Sync, recovery, and cross-device flows are what consumers expect. The UX win is real, and the backup-eligibility flags let you set a sane policy instead of blocking the ecosystem.
Privileged access: device-bound security keys
Admin consoles, break-glass accounts, and anything holding signing keys benefit from attestation, AAGUID allowlisting, and a private key that provably never left hardware. No cloud dependency, no provider-mediated recovery.
Hybrid is the common pattern
Passkeys for the general user base, security keys for administrators and high-assurance roles. Both ride the same WebAuthn ceremony, so the server-side implementation cost is shared.
Relying-party implementation checklist
- Decide
residentKeyper flow:"required"for passkey-style flows,"preferred"as a graceful middle ground. - Set a
userVerificationpolicy:"required"for anything sensitive. - Choose an attestation policy:
"none"for most products,"direct"where hardware proof matters. - AAGUID allowlist for privileged roles; treat unknown AAGUIDs as untrusted there.
- Read the
be/bsflags and enforce your backup policy, and do not silently accept synced credentials where device-binding is required. - Do not hard-fail on
signCount: 0, because platform authenticators frequently do not increment it. - Remember you cannot reliably tell "passkey" from "security key" from the protocol alone. Combine
authenticatorAttachment, AAGUID, attestation, and backup flags as signals.
A registration that accepts both looks like this:
navigator.credentials.create({
publicKey: {
challenge: new Uint8Array([...]), // fresh, per-registration
rp: { id: "example.com", name: "Example" },
user: {
id: new Uint8Array([...]), // stable, not the email
name: "alice@example.com",
displayName: "Alice"
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 } // RS256
],
authenticatorSelection: {
residentKey: "preferred", // passkey flow; "required" for strict
userVerification: "required" // biometric/PIN or reject
},
attestation: "none" // "direct" for privileged roles
}
});The server side stays the same regardless of which authenticator the user picks: verify the challenge, the origin, the signature, and the flags you care about, and verify against the algorithm stored with the credential, never one the message names (the JWT algorithm confusion lesson).
Migrating from passwords or OTP
Migrating without breaking edge cases means treating WebAuthn as an additional credential, not a hard cutover, until enrollment is high enough to retire the old path:
- Enroll during an authenticated session: never require WebAuthn to log in before the user has registered a credential. Prompt for enrollment post-login, not at the login screen.
- Keep the old factor as fallback: until passkey/security-key coverage is high, password+OTP stays available for accounts that have not enrolled, and as recovery when the only registered authenticator is lost.
- Handle the no-capable-device case: users on older Android, locked-down corporate browsers, or hardware without a platform authenticator need a documented fallback, not a dead end.
- Plan account recovery separately: a lost phone or key should not equal a lost account. Backup codes, a second enrolled authenticator, or an out-of-band identity check, not a password reset email, which reintroduces the phishing surface you migrated away from.
- Break-glass for admins: privileged accounts need a recovery path that does not depend on a single device and is itself audited, since it is the path an attacker will target first.
- Roll out in waves: internal users or a volunteer cohort first, so unregistered edge cases (shared devices, kiosk accounts, service accounts that cannot hold a hardware key) surface before a forced org-wide cutover.
Retire the legacy factor only after enrollment and successful-login telemetry both clear a threshold you set in advance, not on a calendar date.
Frequently asked questions
- What is the difference between FIDO2 and passkeys?
- FIDO2 is a protocol family: WebAuthn for the browser-to-server API and CTAP2 for the client-to-authenticator protocol. A passkey is a product concept built on it: a discoverable WebAuthn credential that is usually synced by a platform provider. Every passkey is a FIDO2 credential, but not every FIDO2 credential is a passkey.
- Are passkeys phishing-resistant?
- Yes. Credentials are origin-bound key pairs with challenge-response authentication and no shared secret, so a credential minted for the real site is useless on a lookalike domain. They do not protect the session after sign-in: an adversary-in-the-middle proxy can still steal the session cookie.
- Can a relying party tell a passkey from a security key?
- Not reliably from the protocol alone. Combine authenticatorAttachment, the AAGUID, the attestation statement, and the backup-eligibility (be) and backup-state (bs) flags as signals, and enforce policy on those rather than on the label.
- Should administrators use passkeys or hardware security keys?
- For privileged access, prefer device-bound security keys with attestation and an AAGUID allowlist, so the private key provably never leaves hardware. Synced passkeys suit the general user base; many deployments use both on the same WebAuthn server code.
Sources
- W3C: Web Authentication: An API for accessing Public Key Credentials (Level 3)
- FIDO Alliance: Passkeys overview and the CTAP2 specification
- passkeys.dev (ecosystem and support matrix)