spinny:~/writing $ vim passkeys-webauthn-passwordless-authentication.md
1~2Passwords are one of those things that we've normalized just because we've lived with them for years. Users forget them, reuse them, write them where they shouldn't. Teams must manage resets, policies, hashes, leaks, phishing and support.3~4The passkey don't make authentication perfect, but they remove a huge problem: the server no longer has to keep a secret shared with the user.5~6## What really happens7~8A passkey is a credential based on WebAuthn. When the user creates it, the device generates a key pair:9~10- a private key, which remains on the device or in the password manager;11- a public key, which the server can save.12~13When logging in the server does not ask "tell me the password". Send a random challenge. The device signs it with the private key. The server verifies the signature with the public key.14~15That's the nice part: if the database is stolen, there are no passwords inside to crack. And if a user ends up on a fake domain, the passkey is not valid for that domain. It's not just convenience, it's concrete protection against phishing.16~17## The browser acts as a bridge18~19In the browser the two main API are:20~21- `navigator.credentials.create()` to create a passkey;22- `navigator.credentials.get()` to use it during login.23~24But the important logic is on the server. The server must generate the challenge, save it temporarily, verify the response, check the source and Relying Party ID, then create the session.25~26The client part should be almost boring:27~28```typescript29const options = await fetch('/api/passkeys/login/options').then((r) => r.json());30~31const credential = await navigator.credentials.get({32 publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options),33});34~35await fetch('/api/passkeys/login/verify', {36 method: 'POST',37 headers: { 'Content-Type': 'application/json' },38 body: JSON.stringify(credential?.toJSON()),39});40```41~42If you find yourself implementing WebAuthn encryption by hand, stop. Use a robust server-side library. The errors here are not "cute bugs", they are authentication holes.43~44## What to save in the database45~46There's no need to save half the world. Usually enough:47~48- ID of the credential;49- public key;50- connected user;51- any verification counter or metadata;52- transports, if useful for the UX;53- name chosen by the user;54- creation date and last use.55~56A minimal table might look like this:57~58```sql59create table passkey_credentials (60 id uuid primary key default gen_random_uuid(),61 user_id uuid not null references users(id),62 credential_id text not null unique,63 public_key text not null,64 name text,65 created_at timestamptz not null default now(),66 last_used_at timestamptz67);68```69~70Then I would add audit logs and notifications: if someone creates a new passkey on my account, I want to know about it.71~72## UX matters more than demo73~74The demo of a passkey is always beautiful: you click, Face ID, you're in. The actual product is more complicated.75~76Someone changes their phone. Someone uses a locked company computer. Some people don't understand why the browser suggests a passkey. Someone loses access to their device.77~78This is why I wouldn't start with "from today no more passwords for everyone". I would start like this:79~801. passkey optional for internal users;812. suggestion to create one after a successful login;823. account page to rename and remove passkey;834. clear fallback;845. gradual rollout in the main login.85~86The text in the interface should be simple. "Use your device's lock screen" is better than "authenticate with a resident FIDO2 credential."87~88## Mistakes I would avoid89~90Do not generate challenges on the client. The challenge is created on the server and must be verified only once.91~92Don't just trust the credential ID. You need to verify signature, challenge, origin and Relying Party ID.93~94Don't delete fallbacks before you have a good recovery flow. Passwordless doesn't have to become "if you lose your phone you're out forever".95~96Don't treat the passkey as a purely frontend button. Hiding a button is not security: the real verification is server-side.97~98## Passkey-first or passkey-friendly?99~100For a new product you can think passkey-first. For an existing app I prefer passkey-friendly: add passkey as a recommended method, measure success and problems, then calmly reduce password weight.101~102The ideal migration is not felt. The user discovers that logging in is easier, not that the company has changed its authentication protocol.103~104## Conclusion105~106The passkey are interesting because they improve security and UX at the same time, which is rare. They are not a magic wand: recovery, compatibility, support and rollout remain to be designed well.107~108But the basic change is strong. Stop asking users to invent and protect secrets. You let the device sign a cryptographic proof tied to your domain. Less human memory, less phishing, less password resets. I'd say they're worth taking seriously.109~110## Sources111~112- [MDN: Passkeys](https://developer.mozilla.org/en-US/docs/Web/Security/Authentication/Passkeys)113- [MDN: Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)114- [W3C: Web Authentication](https://www.w3.org/TR/webauthn-3/)115- [passkeys.dev](https://passkeys.dev/)116~
NORMAL · passkeys-webauthn-passwordless-authentication.md [readonly]116 lines · :q to close