Passwords are the worst part of every app. Users reuse them, forget them, get phished, and your support inbox fills with reset requests. In India there is an extra tax on top: SMS OTP, which costs money per message, gets delayed, and fails on weak signal. Passkeys fix all of it.
Passkeys are the passwordless standard built on WebAuthn, now supported by every major browser and operating system. Instead of a shared secret, each account gets a public and private key pair. Your server stores only the public key; the private key never leaves the user's device. Login becomes a biometric tap, and it is phishing-proof by design. This is the developer-honest guide to how they work, what changes on your server, the gotchas, and when to add them.
We build authentication into the web apps and SaaS we ship, usually on Node.js or Laravel backends with Next.js and React front ends, so the notes below come from shipping it, not just reading the spec.
What a passkey actually is
A passkey is a cryptographic key pair tied to one website. When a user registers, their device generates the pair. The private key stays locked on the device, in the secure enclave, TPM, or a password manager, and is unlocked with a fingerprint, face, or device PIN. The public key goes to your server. Nobody, including you, ever sees the private key.
Two flavours exist. Synced passkeys (Apple iCloud Keychain, Google Password Manager, 1Password) sync across a user's devices, so a new phone still has them. Device-bound passkeys (a hardware key like a YubiKey) stay on one device. Both are built on the same open standards: WebAuthn from the W3C, and FIDO2 underneath.
Why passkeys beat passwords and OTP
- Phishing-resistant. The signature a passkey produces is bound to your exact domain. A fake look-alike site cannot get a usable signature, so phishing simply does not work.
- No shared secret. There is no password to steal. A database dump contains only public keys, which are useless to an attacker.
- No reuse. Each passkey is unique to your site, so a breach somewhere else cannot touch your users.
- No SMS OTP. Passkeys remove the per-message cost and the 'I did not get the code' support tickets that come with OTP, which is a real saving in India.
- Faster. A biometric tap beats typing a password and waiting for a code to arrive.
The two ceremonies: registration and login
WebAuthn has exactly two flows. Get these right and the rest is detail.
Registration (attestation)
The user is signing up or adding a passkey to an existing account. Your server generates a random challenge and sends it with your rpId (your domain). The browser calls navigator.credentials.create(), the device generates a key pair, and the user confirms with their biometric or PIN. The browser returns the new public key and a credential ID, and your server stores them against that user.
Login (assertion)
Your server generates a fresh random challenge. The browser calls navigator.credentials.get(), the device finds the matching private key, and the user confirms with their biometric. The device signs the challenge, and your server verifies the signature with the stored public key, checks that the challenge is the one it issued, that the origin and rpId match, and that the sign counter advanced. If all of that passes, they are logged in.
What changes on your server
For passkey users you stop storing a password hash and start storing, per credential:
- Credential ID. The handle that identifies this specific key.
- The public key. Used to verify every future login signature.
- The sign counter. A number that increments each use. If it ever goes backwards, that signals a cloned key, and you can block it.
- The user handle and rpId. So you know which user and which domain the credential belongs to.
On each login you verify four things: the signature matches the stored public key, the challenge is the single-use one you issued, the origin and rpId match your domain, and the sign counter moved forward. Do not hand-roll the cryptography. On Node we use SimpleWebAuthn; Laravel and most stacks have a mature WebAuthn package. The library handles the crypto so you handle the storage and the flow.
The gotchas that trip people up
- rpId and origin must match exactly. A passkey registered on www.yoursite.com will not work on yoursite.com. Decide your canonical domain first, then register against it.
- Challenges must be random, server-generated, and single-use. Reusing a challenge breaks the entire security model. Generate one per attempt and throw it away after.
- Always keep a fallback. Not every user or device has passkeys yet, and people borrow devices. Offer passkeys alongside password or an email magic link, and let users register more than one passkey.
- Account recovery is the hard part. If a user loses every device, you need a recovery path: a backup email magic link, a second passkey, or one-time recovery codes. Synced passkeys reduce this, but plan for it before launch.
- Conditional UI (autofill) is the good UX. The passkey should appear in the login field's autofill prompt. It needs the autocomplete='webauthn' attribute and a slightly different call, and it is worth the effort.
When to add passkeys, and when to wait
Add them when account security genuinely matters (fintech, SaaS, dashboards, anything with payments or sensitive data), when you are paying for SMS OTP at scale and want that cost gone, or when password-reset tickets are eating your support time. Those are the cases where passkeys pay for themselves quickly.
Wait, or at least do not rush, when you have a tiny user base with no sensitive data and the added auth surface buys little, or when you do not yet have a fallback and recovery plan. Passkeys are additive: you can ship them next to passwords and migrate users over time, which is exactly how we recommend rolling them out. There is no need for a risky big-bang swap.
How we add passkeys to a product
We add passkeys as an additive layer, never a big-bang replacement. The shape on a Node.js backend: a credentials table (credential ID, public key, sign counter, user handle), four endpoints (begin and finish registration, begin and finish login) wired through SimpleWebAuthn, conditional-UI autofill on the Next.js login form, and a clean fallback to the existing login. On Laravel it is the same shape with a WebAuthn package. We keep password and email login working, let users add multiple passkeys, and build a recovery path before launch. It drops straight into a SaaS or CRM login without disrupting existing users.
Common questions about passkeys
Do passkeys replace passwords completely?
Eventually, but roll them out additively. Offer passkeys alongside passwords so users without them, or on a borrowed device, can still log in. As adoption grows you can nudge everyone to passkeys and retire passwords. A hard cut-over on day one is risky and unnecessary.
What happens if a user loses their phone?
With synced passkeys (Apple iCloud Keychain, Google Password Manager, 1Password) the passkey syncs to their other devices and to a new phone, so losing one device is not a lockout. For device-bound passkeys or single-device users you need a recovery path: a backup email magic link, a second registered passkey, or one-time recovery codes. Plan recovery before you launch.
Are passkeys actually phishing-proof?
Yes, by design. The signature a passkey produces is cryptographically bound to your exact domain. A fake site at a look-alike domain cannot get a usable signature, because the browser refuses to use the passkey on the wrong origin. That is the single biggest security win over passwords and OTP, both of which a convincing phishing page can capture.
Do passkeys work across browsers and devices?
Support is now universal across current Chrome, Safari, Edge, and Firefox, and across iOS, Android, macOS, and Windows. Synced passkeys move between a user's devices in the same ecosystem, and cross-ecosystem login is handled by scanning a QR code with the phone that holds the passkey. For practically every user in 2026, it just works.
Will passkeys reduce our SMS OTP costs?
Significantly, if OTP is your current login or second factor. Every passkey login is one less SMS, which in India is a real per-message cost plus the delivery failures and support tickets OTP brings. Many teams add passkeys specifically to cut the OTP bill and the 'I did not get the code' complaints.
How long does it take to add passkeys to an existing app?
For a typical web app with email and password login, a clean passkey integration is usually 1 to 3 weeks: the credentials table, the four WebAuthn endpoints, the front-end calls with autofill, a recovery path, and testing across devices. Most of the time goes into recovery and cross-device testing, not the happy path.
Honest summary
Passkeys are the first genuinely better replacement for the password: phishing-resistant by design, nothing for attackers to steal, no SMS OTP, and a one-tap login. They are built on WebAuthn, supported everywhere, and you add them additively without breaking existing logins. The hard part is not the cryptography, a library handles that. It is the boring but critical pieces: matching rpId to your canonical domain, single-use challenges, a fallback, and a real account-recovery path.
If you run a SaaS, a fintech product, or any app where a breach or an OTP bill hurts, passkeys are worth adding now. The cost calculator gives a rough estimate, or send us a WhatsApp message with your stack and login setup and we will reply within 24 hours with a plan.
Want passwordless login that kills phishing and SMS OTP costs? We add passkeys (WebAuthn) to web apps and SaaS in Noida on Node.js and Laravel, additively, with a proper recovery path and cross-device testing. It drops into your existing login without disrupting users. → Add passkeys to your app
Top comments (0)