Passwords 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.
The 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.
What really happens
A passkey is a credential based on WebAuthn. When the user creates it, the device generates a key pair:
- a private key, which remains on the device or in the password manager;
- a public key, which the server can save.
When 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.
That'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.
The browser acts as a bridge
In the browser the two main API are:
navigator.credentials.create()to create a passkey;navigator.credentials.get()to use it during login.
But 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.
The client part should be almost boring:
const options = await fetch('/api/passkeys/login/options').then((r) => r.json()); const credential = await navigator.credentials.get({ publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options), }); await fetch('/api/passkeys/login/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credential?.toJSON()), });
If 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.
What to save in the database
There's no need to save half the world. Usually enough:
- ID of the credential;
- public key;
- connected user;
- any verification counter or metadata;
- transports, if useful for the UX;
- name chosen by the user;
- creation date and last use.
A minimal table might look like this:
create table passkey_credentials ( id uuid primary key default gen_random_uuid(), user_id uuid not null references users(id), credential_id text not null unique, public_key text not null, name text, created_at timestamptz not null default now(), last_used_at timestamptz );
Then I would add audit logs and notifications: if someone creates a new passkey on my account, I want to know about it.
UX matters more than demo
The demo of a passkey is always beautiful: you click, Face ID, you're in. The actual product is more complicated.
Someone 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.
This is why I wouldn't start with "from today no more passwords for everyone". I would start like this:
- passkey optional for internal users;
- suggestion to create one after a successful login;
- account page to rename and remove passkey;
- clear fallback;
- gradual rollout in the main login.
The text in the interface should be simple. "Use your device's lock screen" is better than "authenticate with a resident FIDO2 credential."
Mistakes I would avoid
Do not generate challenges on the client. The challenge is created on the server and must be verified only once.
Don't just trust the credential ID. You need to verify signature, challenge, origin and Relying Party ID.
Don'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".
Don't treat the passkey as a purely frontend button. Hiding a button is not security: the real verification is server-side.
Passkey-first or passkey-friendly?
For 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.
The ideal migration is not felt. The user discovers that logging in is easier, not that the company has changed its authentication protocol.
Conclusion
The 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.
But 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.