Back to the blog
Securityยท Jul 12, 2026ยท 9 min read

Passkeys in production: a practical WebAuthn migration guide

Passwords are finally on their way out. What we learned rolling out passkeys across a multi-product platform: enrollment flows, fallback strategy, and the edge cases nobody warns you about.

#auth#passkeys#webauthn#security

Passkey support crossed the usability threshold sometime in 2025: every major browser, both mobile platforms, and every password manager now handle them natively. In 2026 the question isn't whether to support passkeys โ€” it's how to migrate an existing password-based user base without locking anyone out. Here's the playbook we used across the Appesto platform.

The mental model: a keypair per site, synced by the platform

A passkey is a WebAuthn credential: a public/private keypair generated by the user's device, scoped to your domain. The private key never leaves the authenticator (or its encrypted sync fabric โ€” iCloud Keychain, Google Password Manager, 1Password). Your server stores only the public key. There is nothing to phish, nothing to reuse across sites, and nothing to leak in a database breach. Domain scoping is also why passkeys kill phishing dead: a credential registered for appesto.com simply will not respond on appesto-login.evil.com.

Migration order matters

  • Phase 1: offer passkey enrollment to signed-in users (post-login prompt, settings page). Password stays as fallback. This builds coverage with zero lockout risk.
  • Phase 2: make passkeys the default sign-in UX - show the passkey prompt first, with 'use password instead' one click away.
  • Phase 3 (months later): allow passkey-only accounts for new signups, and let existing users delete their password once they have two registered authenticators.
  • Never force-delete passwords server-side. Users with one registered device and no sync will get locked out the day they drop their phone in a lake.

The server side is smaller than you think

// Registration verification with @simplewebauthn/server
import { verifyRegistrationResponse } from '@simplewebauthn/server';

const verification = await verifyRegistrationResponse({
  response: body.credential,
  expectedChallenge: session.challenge,
  expectedOrigin: 'https://appesto.com',
  expectedRPID: 'appesto.com',
});

if (verification.verified && verification.registrationInfo) {
  const { credential } = verification.registrationInfo;
  await db.insert(authenticators).values({
    userId: session.userId,
    credentialId: credential.id,
    publicKey: credential.publicKey,
    counter: credential.counter,
    transports: body.credential.response.transports,
  });
}

Two server-side rules that save you pain later: store the transports array so the browser can route the next sign-in to the right authenticator without guessing, and treat the signature counter as advisory โ€” synced passkeys legitimately report zero forever, so a strict counter check will reject real users.

Edge cases from the real rollout

  • Corporate Windows machines with Windows Hello disabled by group policy silently fail enrollment - detect with PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() and hide the prompt.
  • Users share accounts more than you think. Passkeys are per-person by design, which surfaces every shared login in your user base as a support ticket.
  • Cross-device sign-in (scanning a QR code with your phone) works remarkably well but needs Bluetooth proximity - it fails inside VMs and remote desktops, so keep a fallback path.
  • Account recovery becomes your weakest link. If recovery is 'email a magic link', your account security equals your email security - be honest about that in your threat model.

What we'd tell a team starting today

Use a maintained library (@simplewebauthn or your auth provider's built-in support) rather than hand-rolling CBOR parsing. Ship phase 1 in a week, watch enrollment rates, and let the data pace the rollout. Our numbers: 40% of active users enrolled a passkey within three months when prompted post-login, and passkey sign-ins have a measurably higher success rate than password entry on mobile โ€” fewer typos, no reset flow, faster by seconds.

Written by Appesto Engineering.