---
title: "Passkeys and WebAuthn: Passwordless Auth Guide for Apps 2026"
author: "Nate Laquis"
author_role: "Founder & CEO"
date: "2029-10-17"
category: "Technology"
tags:
  - passkeys WebAuthn implementation guide
  - passwordless authentication
  - FIDO2 passkeys
  - biometric login
  - web authentication API
excerpt: "Passwords are the weakest link in your app's security. This guide walks through implementing passkeys with WebAuthn across iOS, Android, and web, including credential flows, fallback strategies, and migrating existing users off passwords."
reading_time: "14 min read"
canonical_url: "https://kanopylabs.com/blog/passkeys-webauthn-passwordless-auth-guide"
---

# Passkeys and WebAuthn: Passwordless Auth Guide for Apps 2026

## Why Passkeys Are No Longer Optional

Passwords have been the default authentication mechanism for decades, and they have been failing for just as long. In 2025, credential stuffing attacks accounted for over 80% of web application breaches according to the FIDO Alliance. SIM swapping attacks rendered SMS-based MFA unreliable. Phishing campaigns got so convincing that even security-aware engineers fell for them. The industry has known passwords are broken for years. What changed is that we finally have a viable replacement that works across every major platform.

Passkeys, built on the WebAuthn standard (part of the broader FIDO2 specification), use public-key cryptography to eliminate shared secrets entirely. There is no password to steal, no OTP to intercept, no SMS code to redirect. The private key never leaves the user's device, and authentication happens through biometrics or a device PIN. Apple, Google, and Microsoft have all committed to passkeys as their primary authentication method, which means the ecosystem support is real and growing.

If you are building or maintaining an app in 2026, the question is no longer whether to support passkeys. It is how quickly you can implement them and how gracefully you can migrate your existing users. This guide covers the full implementation path: credential creation, cross-device authentication, platform-specific details for iOS, Android, and the web, fallback strategies for edge cases, and a practical migration plan for apps that currently rely on passwords.

![Security infrastructure and compliance systems representing modern passwordless authentication](https://images.unsplash.com/photo-1563986768609-322da13575f2?w=800&q=80)

## How Passkeys and WebAuthn Actually Work

Before you write a single line of code, you need to understand the underlying protocol. Passkeys are discoverable FIDO2 credentials stored in a platform authenticator (your phone, laptop, or security key) and optionally synced across devices via a cloud keychain. WebAuthn is the browser API that lets your web app interact with those authenticators. Together, they replace the entire password lifecycle: registration, login, and account recovery.

### The Registration Flow (Credential Creation)

Registration starts on your server. You generate a challenge (a random byte array), specify your relying party information (your domain), and define user information (a unique user ID, display name). This gets sent to the client as a **PublicKeyCredentialCreationOptions** object.

On the client, you call **navigator.credentials.create()** with those options. The browser prompts the user to verify their identity via Face ID, fingerprint, Windows Hello, or a device PIN. If verification succeeds, the authenticator generates a new asymmetric key pair. The public key, a credential ID, and attestation data are sent back to your server. The private key stays on the device, protected by the platform's secure enclave.

Your server validates the attestation, stores the public key and credential ID linked to the user account, and registration is complete. The user never typed a password. There is nothing in your database that an attacker can use to impersonate the user.

### The Login Flow (Assertion)

Login follows a similar pattern. Your server generates a challenge and sends it to the client as **PublicKeyCredentialRequestOptions**. You can optionally include a list of allowed credential IDs (for non-discoverable credentials), but with passkeys you typically omit this and let the authenticator present all credentials for your domain.

The client calls **navigator.credentials.get()**. The browser shows a credential picker if multiple passkeys exist for the domain. The user verifies with biometrics, the authenticator signs the challenge with the private key, and the signed assertion is sent to your server. Your server looks up the public key by credential ID, verifies the signature, checks the challenge, and grants access.

### What Makes Passkeys Different from Old-Style WebAuthn

WebAuthn has been around since 2018, but early implementations used device-bound credentials. If you registered a security key on your laptop, that credential existed only on that laptop. Lose the device, lose access. Passkeys solve this by enabling credential sync across devices through the platform's cloud keychain. An Apple passkey created on your iPhone syncs to your Mac and iPad via iCloud Keychain. A Google passkey syncs across all your Android devices and Chrome browsers. This sync mechanism is what makes passkeys viable as a primary authentication method for mainstream users.

## Implementing Passkeys on the Web with SimpleWebAuthn

The raw WebAuthn API works, but it is verbose and requires careful handling of binary data (ArrayBuffers, base64url encoding). For production implementations, you want a library that handles the encoding, attestation verification, and assertion validation for you. [SimpleWebAuthn](/blog/how-to-build-secure-authentication) is the most widely adopted library in the JavaScript ecosystem, and it is what we recommend for most web projects.

### Server-Side Setup

Install **@simplewebauthn/server** on your backend. This package provides two core functions: **generateRegistrationOptions()** and **verifyRegistrationResponse()** for registration, plus **generateAuthenticationOptions()** and **verifyAuthenticationResponse()** for login. You configure your relying party ID (your domain, like "yourapp.com"), your relying party name (displayed to the user), and supported authenticator types.

For the registration flow, generate options with a user ID (use an opaque byte array, not an email), a user display name, and any existing credentials for that user (so the authenticator does not create duplicates). Set **authenticatorSelection.residentKey** to "required" and **authenticatorSelection.userVerification** to "preferred." These settings ensure the authenticator creates a discoverable passkey with biometric verification when available.

### Client-Side Setup

Install **@simplewebauthn/browser** on your frontend. This package provides **startRegistration()** and **startAuthentication()** functions that wrap the raw WebAuthn API calls, handle binary encoding, and return clean JSON objects. Your registration code fetches options from your server, calls **startRegistration(options)**, and posts the result back to your server for verification. Login is the same pattern with **startAuthentication()**.

### Storing Credentials

Your database needs a credentials table with at minimum: the credential ID (base64url-encoded, unique index), the public key (base64url-encoded), a sign counter (integer, used to detect cloned authenticators), the credential device type (singleDevice or multiDevice), whether the credential is backed up (boolean), and a foreign key to your users table. A user can have multiple passkeys (one per device ecosystem), so this is a one-to-many relationship.

One detail that trips up many teams: the credential ID can be up to 1,023 bytes long. Do not use a VARCHAR(255) column. Use TEXT or a sufficiently large VARCHAR.

![Developer implementing WebAuthn passkey authentication code in a modern IDE](https://images.unsplash.com/photo-1555949963-ff9fe0c870eb?w=800&q=80)

## Platform-Specific Implementation: iOS, Android, and Beyond

Web passkeys cover your browser-based apps, but if you ship native mobile apps, the implementation path is different on each platform. The good news: both Apple and Google have made passkey support a first-class feature in their SDKs.

### iOS and macOS (Apple Platforms)

Apple introduced passkey support in iOS 16 and macOS Ventura. On Apple platforms, passkeys are managed through the **AuthenticationServices** framework. You create a passkey registration request using **ASAuthorizationPlatformPublicKeyCredentialProvider**, present it via **ASAuthorizationController**, and the system handles the biometric prompt (Face ID or Touch ID) and key generation.

Passkeys created on Apple devices sync across all devices signed into the same Apple Account via iCloud Keychain. This sync is end-to-end encrypted and requires the device passcode to decrypt on a new device. For enterprise apps distributed through managed Apple IDs, be aware that iCloud Keychain may be disabled by MDM policy, which means passkeys will not sync and each device needs its own credential.

One important consideration for iOS: Apple requires that your associated domain file (apple-app-site-association) includes a "webcredentials" entry matching your relying party ID. Without this, passkey creation will fail silently. Set this up in your app's Associated Domains entitlement and host the file at **/.well-known/apple-app-site-association** on your server.

### Android

Android passkey support is delivered through the **Credential Manager API**, which shipped in Android 14 and was backported to Android 9+ via Google Play Services. You create a **CreatePublicKeyCredentialRequest** with your WebAuthn options JSON, pass it to **CredentialManager.createCredential()**, and handle the result. Login uses **GetCredentialRequest** with a **GetPublicKeyCredentialOption**.

Passkeys on Android sync through Google Password Manager. Users can also use passkeys stored on other devices (like an iPhone) via the cross-device authentication flow (more on that below). For Android apps, you need to configure your **assetlinks.json** file at **/.well-known/assetlinks.json** to associate your domain with your app's package name and signing certificate.

### React Native and Cross-Platform Frameworks

If you are building with React Native, the **react-native-passkeys** package provides a bridge to the native passkey APIs on both platforms. Expo users can leverage **expo-passkeys** (available since Expo SDK 52). These libraries expose a JavaScript API that mirrors the WebAuthn browser API, so your registration and login logic stays consistent across platforms. For Flutter, the **passkeys** package from corbado wraps the native APIs similarly.

## Cross-Device Authentication and the QR Code Flow

One of the most impressive aspects of the passkey specification is cross-device authentication. A user can sign in on a desktop computer using a passkey stored on their phone, even if the phone and computer are in completely different ecosystems (for example, an Android phone and a Windows laptop).

### How It Works

When a user initiates a passkey login on a device that does not have a matching credential, the browser displays a QR code. The user scans the QR code with their phone (which holds the passkey). The two devices establish a secure Bluetooth Low Energy (BLE) proximity connection to verify the phone is physically near the computer. The phone prompts for biometric verification, signs the challenge, and sends the assertion back through the BLE channel. The browser forwards it to your server.

From your server's perspective, this is identical to a normal passkey login. You receive the same assertion payload and verify it the same way. The cross-device transport is handled entirely by the operating system and browser. You do not need to implement any of the BLE or QR code logic yourself.

### Practical Considerations

BLE needs to be enabled on both devices for cross-device auth to work. This trips up some users who keep Bluetooth disabled for battery reasons. Your error messaging should account for this: if a passkey login fails, suggest checking Bluetooth settings as a troubleshooting step.

Cross-device authentication is slower than a direct passkey login (3 to 5 seconds versus under 1 second). It is a great fallback, but you should encourage users to create device-local passkeys on each device they use regularly. After a successful cross-device login, prompt the user to create a passkey on the current device. This improves the experience on subsequent logins and reduces dependency on the phone being nearby.

Enterprise environments often restrict Bluetooth on managed desktops. If you are building for corporate users, account for this limitation in your auth flow by offering alternative methods when cross-device passkey auth is not available.

## Fallback Strategies and Account Recovery

Passkeys are more reliable than passwords in most scenarios, but you still need fallback plans. Devices break, get lost, or get stolen. Users switch ecosystems (from iPhone to Android) and may not have immediate access to their synced credentials. A [well-designed auth system](/blog/auth0-vs-clerk-vs-firebase-auth) always has a recovery path.

### Multiple Passkeys Per Account

The simplest fallback is encouraging users to register passkeys on multiple devices and across ecosystems. A user with a passkey on their iPhone (synced via iCloud) and another on their Android tablet (synced via Google Password Manager) has two independent recovery paths. Your UI should make it trivial to add additional passkeys from the account settings page. Display registered credentials with device metadata so users can see at a glance which devices have passkeys.

### Recovery Codes

Generate a set of one-time recovery codes during initial passkey registration. Display them once, require the user to confirm they saved them (a checkbox is not enough; consider asking them to enter one code back), and hash them before storing in your database. Each code works once. This is the same pattern used by TOTP-based MFA, and users are generally familiar with it.

### Email-Based Recovery

For lower-security applications, a magic link sent to the user's verified email can serve as a recovery method. The user clicks the link, verifies their identity, and registers a new passkey. This is inherently weaker than passkey-only auth (email accounts can be compromised), but it is a reasonable trade-off for consumer apps where the cost of account lockout is high.

### Keeping Passwords as a Temporary Fallback

During migration (covered in the next section), many apps maintain password auth alongside passkeys. This is fine as a transitional strategy, but set a timeline for deprecation. The longer you support both methods, the longer you carry the security risks of passwords. Consider a phased approach: first, make passkeys the default and hide password login behind a "more options" link. Then, after a migration threshold (say, 80% of active users have passkeys), send remaining password-only users through a forced passkey enrollment flow.

### What Not to Do

Do not use SMS-based recovery. SIM swapping attacks make SMS unreliable for account recovery, and relying on it undermines the security gains you achieved by adopting passkeys. Similarly, avoid security questions. They are trivially guessable or researchable and add friction without meaningful security.

## Migrating Existing Users from Passwords to Passkeys

Greenfield apps have it easy: ship with passkeys from day one and never deal with passwords. But most real-world products have an existing user base authenticating with email and password, possibly with TOTP-based MFA. Migrating those users to passkeys requires patience, clear communication, and a phased rollout.

### Phase 1: Offer Passkeys Alongside Passwords (Weeks 1 to 4)

Add passkey support without removing anything. After a successful password login, show a prompt: "Secure your account with a passkey. Sign in with your face or fingerprint instead of a password." Make it easy to dismiss but hard to miss. Track enrollment rates. You are looking for early adopters who will validate the flow before you push harder.

During this phase, invest in instrumentation. Log passkey registration success and failure rates, the authenticator types being used (platform authenticator versus roaming), and cross-device usage patterns. This data shapes your decisions in later phases.

### Phase 2: Make Passkeys the Default (Weeks 4 to 12)

Redesign your login page so passkey auth is the primary action. The login screen should show a "Sign in with passkey" button prominently, with "Sign in with password" as a secondary link below the fold. For users who have enrolled a passkey, auto-trigger the passkey flow when they land on the login page (browsers support this via conditional mediation / autofill-assisted requests using **mediation: "conditional"** in the WebAuthn get options).

Send email campaigns to users who have not yet enrolled. Explain the benefits (faster login, no password to remember, phishing-proof). Include a direct link to the passkey enrollment flow in your app. Aim for 60 to 80% passkey adoption by the end of this phase.

### Phase 3: Deprecate Passwords (Weeks 12 to 24)

For users who still rely on passwords, show increasingly firm prompts to enroll a passkey. Set a concrete deprecation date and communicate it clearly. When the date arrives, require passkey enrollment at next login: the user enters their password one final time, then must create a passkey to continue. After passkey creation, disable password login for that account.

Keep the password hash in your database for a grace period (90 days) in case of account recovery edge cases, then delete it. Once passwords are fully deprecated, you can simplify your auth stack significantly: no more password hashing, no more brute force protection on login endpoints, no more credential stuffing mitigation. The security and operational benefits compound.

![Laptop displaying code for migrating user authentication from passwords to passkeys](https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=800&q=80)

## Costs, Timelines, and Choosing a Passkey Provider

How long does a passkey implementation take and what does it cost? The answer depends on whether you are building from scratch, adding passkeys to an existing auth system, or using a managed provider.

### Building with SimpleWebAuthn (Self-Hosted)

If your app already has a custom auth system (session-based or JWT-based), adding passkeys with SimpleWebAuthn takes a senior full-stack developer 2 to 3 weeks. That covers the registration and login flows, credential storage, the account settings UI for managing passkeys, fallback handling, and cross-device testing. Budget an additional week for mobile-specific work if you have native apps. Total cost at agency rates: roughly $15,000 to $25,000 depending on complexity. The ongoing cost is minimal since SimpleWebAuthn is open source and you are hosting the auth yourself.

### Using a Managed Auth Provider

If you are using [Clerk, Better Auth, or a similar provider](/blog/clerk-vs-better-auth-vs-lucia), passkey support may already be available as a configuration toggle or plugin. Clerk shipped passkey support in late 2025 and it is included on all plans. Better Auth has a passkeys plugin that wraps SimpleWebAuthn with a clean API. Auth0 supports passkeys on their enterprise plans. In these cases, implementation time drops to 1 to 3 days for basic integration, plus a week for migration flow design and testing.

### Dedicated Passkey Providers

Companies like Corbado, Hanko, and Passage (by 1Password) specialize in passkey-first authentication. They offer drop-in UI components, backend APIs, and analytics dashboards for passkey adoption. Pricing typically runs $0.05 to $0.10 per authentication event or $100 to $500 per month on flat plans. These make sense if passkeys are your only auth method and you want to outsource the entire implementation, including the UI. For most apps that already have an auth system, adding passkeys through your existing provider or SimpleWebAuthn is more cost-effective.

### Testing Across Devices and Browsers

Budget time for cross-device testing. Passkey implementations behave differently across Safari, Chrome, Firefox, and Edge. iOS and Android have distinct flows. Windows Hello, Touch ID, and Android biometrics all have their own UX patterns. You need to test on real devices, not just emulators. BrowserStack and LambdaTest support WebAuthn testing, but nothing replaces having an iPhone, an Android phone, a Mac, and a Windows laptop on your desk.

## Ship Passkeys Now, Not Later

Passkeys are the biggest improvement to application security in a generation. They eliminate phishing, remove the operational burden of password management, and give users a login experience that is genuinely faster and easier than typing a password. The specification is mature, the ecosystem support is broad, and the libraries are production-ready.

The cost of waiting is real. Every month you delay passkey adoption, your users remain vulnerable to credential stuffing, phishing, and password reuse attacks. Every new user you onboard with a password is a user you will eventually need to migrate. Start now, even if it is just Phase 1: offering passkeys alongside your existing auth.

If you are building a new app, make passkeys the primary authentication method from day one. If you have an existing app with password-based auth, start the migration. The technical work is well-understood, the tools are mature, and the security improvements are immediate.

We have helped teams implement passkey authentication across web and mobile platforms, from initial architecture through migration rollout. If you want to get passkeys right the first time, [book a free strategy call](/get-started) and we will walk through the best approach for your stack.

---

*Originally published on [Kanopy Labs](https://kanopylabs.com/blog/passkeys-webauthn-passwordless-auth-guide)*
