Phishing-Resistant Auth · Chapter 4 of 5
Passkeys in practice: account model, recovery pitfalls
Model credentials as a many-to-one relationship, design for lost devices from day one, and avoid recovery flows that quietly reintroduce phishing.
The protocol is the easy part. Teams that ship passkeys successfully spend most of their effort on the account model around the credential: how many a user may have, how they are named and revoked, what happens when a phone is lost, and how a recovery flow avoids becoming the weakest door in the building. This chapter covers those decisions in the order you will meet them.
Many credentials per account
Model credentials as a separate table with a many-to-one relationship to accounts. A typical user will end up with a passkey synced through their phone’s platform account, perhaps another synced through a desktop password manager, and possibly a hardware security key. Each is a distinct credential with its own ID and public key. Never assume one passkey per user, and never overwrite an existing credential when a new one is registered.
create table webauthn_credentials (
id bytea primary key, -- credential ID from the authenticator
account_id uuid not null references accounts(id),
public_key bytea not null, -- COSE_Key
sign_count bigint not null default 0,
backup_eligible boolean not null, -- BE flag at registration
backed_up boolean not null, -- BS flag, updated on each use
aaguid uuid,
nickname text, -- “Work laptop”, user-editable
created_at timestamptz not null default now(),
last_used_at timestamptz
);Give users a credentials page listing each passkey with a nickname, the creation date, the last-used date and a revoke button. Pre-fill nicknames from the AAGUID where you can map it to a friendly provider name, and let the user edit them. Revoking must take effect immediately and must require a recent re-authentication, ideally with a different credential.
Synced versus device-bound
Synced passkeys are backed up to the platform account of the user’s phone or password manager and appear on their other devices automatically. They largely solve the lost-device problem, because buying a new phone and signing into the platform account restores them. Device-bound credentials — typical of hardware keys — never leave the device; losing it loses the credential. The BE flag tells you which kind you have at registration, and BS tells you whether it is currently backed up.
This shifts trust. With synced passkeys, the security of your users’ accounts partly depends on the security of their platform accounts. For most consumer services that is an excellent trade, since those platform accounts are heavily defended. For high-assurance populations you may require at least one device-bound credential, or restrict sensitive actions to credentials where BE is false.
Recovery is where phishing comes back
Every authentication system has a recovery path, and attackers read it first. If losing a passkey can be fixed with an emailed link, an SMS code or a support agent who asks for a date of birth, then your phishing-resistant login is exactly as strong as that path. A phishing kit does not need to defeat WebAuthn; it only needs to start the recovery flow and relay whatever it asks for.
- Encourage at least two credentials at enrolment, ideally on different platforms, and nudge again after the first sign-in.
- Offer single-use recovery codes generated at enrolment, stored hashed, shown once, and usable only to register a new credential.
- Put a delay on recovery: notify every existing contact channel and wait, for example, 24 hours before the new credential becomes active.
- Downgrade the session after recovery: no payout changes or credential deletions until the delay has passed.
A recovery story from the bench, an illustrative example: Noor Halvorsen, a freelance designer and Larkspur Ledger customer, loses her only phone on 2026-10-04. She signs in with a recovery code; the system lets her register a new passkey but places the account in a 24-hour hold, emails her verified address and sends a notice to her old phone number. No payout destination may change during the hold. When an attacker tries the same flow against another account two days later with a phished recovery code, the account owner sees the notice and cancels the hold within an hour.
Migration from passwords and OTP
You will run passkeys alongside existing factors for a long time. Offer passkey creation right after a successful sign-in with the old method, when the user is already authenticated and motivated. Use conditional mediation so the passkey appears in the username autofill menu. Do not remove the password the moment a passkey is added; instead, measure how often each user signs in with the passkey, and offer to remove the password once it has been unused for a while.
Keep identifiers boring and stable. The WebAuthn user handle should be a random value unrelated to email or phone, because users change those. When a user changes their email, nothing about their credentials should change. When they delete their account, delete every credential row, so the passkeys left in their platform account fail cleanly with an unknown-credential error rather than signing into a recycled account.
Expect the first months of a rollout to change what support handles. Questions shift from forgotten passwords to which device holds which passkey. A credentials page with clear nicknames and last-used dates answers most of them before they become tickets.
Above all, test recovery with real people before launch. A flow that looks secure on paper but confuses honest users will be bypassed by support staff.
// bench check
0/3 correct · 0 answered
1. Why store the BE and BS flags per credential?
2. A passkey-only login has an SMS-code recovery flow with no delay. Its effective phishing resistance is…
3. What should the WebAuthn user handle contain?