Blog
Explainer

What is a passkey, and which of the two kinds you just got

A passkey is a key pair your user's device holds and proves on your domain. A synced one and a device-bound one behave differently the day a phone dies, and the ceremony tells you which you got.

Dharmendra Jagodana7 min read

In short

A passkey is a public-key credential: the user's device or password manager keeps the private half, your server keeps the public half, and signing in means the device signs a challenge your domain issued. Two kinds ship under that one name, synced and device-bound, and only one of them survives a lost phone.

A passkey is a public-key credential. The user's device or password manager keeps the private half, your server keeps the public half, and signing in means the device signs a fresh challenge for your domain once the user confirms with a fingerprint, a face or a PIN.

Every explainer on this query says that, and it is right. It also stops one step short of the thing that decides how your support queue looks in a year.

Two different credentials ship under the word passkey. One is synced through a credential manager and lands on every device the user owns. The other is stuck on the single device that made it. Same API, same ceremony, same row in your database. Completely different answer to "my phone went in the river".

And the ceremony tells you which one you just got. Most implementations throw that away.

Why they exist

Because a shared secret has two owners and either one can lose it. The user can hand a password to a convincing lookalike page, and you can lose the whole table to a breach, however carefully you hashed it.

A passkey removes the shared secret. There is nothing on your server worth stealing, because the public half is useless on its own, and there is nothing to phish, because the credential only answers on the domain it was created for. A lookalike site at a different hostname does not get a prompt. It gets silence.

That is the whole security argument, and it is the part already written about everywhere. The rest of this page is about what you actually hold afterwards.

How it actually works

Four things happen, and only the middle two are interesting.

Your server generates a random challenge and names the domain the credential will belong to. The browser passes both to whatever credential manager the user has. That manager mints a key pair scoped to that one domain, asks the user to confirm, and hands back the public half with a signature over the challenge for your server to check. Then it stores what came back.

The domain part is not a detail. WebAuthn binds every credential to the hostname that ran the ceremony, which on BuildBase is your organization's auth domain rather than your app or your API. Change that hostname later and the browser stops offering the passkeys registered under the old one, because as far as WebAuthn is concerned they belong to a different site. We store the rpId on every passkey and compare it to the current auth domain, so a credential stranded by a domain move is at least visible rather than mysteriously absent. Settle the domain before anyone enrols, which is the argument running auth on your own domain makes at greater length.

The challenge has a clock on it. Ours is held in Redis for 300 seconds, 5 minutes exactly, and is consumed atomically, so it answers once and once only. A user who starts signing in, wanders off and comes back gets an expired-challenge error instead of a replayable window. They try again. That is the whole cost of the short window, and it is why the window is short.

Here is what the server ends up holding, which is less than people expect:

StoredWhat it is
Credential idThe handle the browser uses to find the right key
Public keyThe half that verifies signatures and decrypts nothing
Signature counterA number the authenticator increments, if it bothers
TransportsHow the authenticator was reached: internal, USB, NFC
Device type, backupWhich of the two kinds this is, and whether it is saved
Relying party idThe hostname this credential will answer on

No secret. Nothing that signs in anywhere else. If someone walks off with that table, they have a list of public keys and the knowledge that your users exist.

The two kinds, and the flag that names them

This is the part the generic explainers skip.

The authenticator reports two things about backup when it creates a credential. Backup eligibility says whether this credential is allowed to leave the device it was born on. Backup state says whether it currently has. The W3C WebAuthn specification is blunt about the difference in lifetime: backup eligibility "is a credential property and is permanent for a given public key credential source", while backup state "can change over time".

So:

EligibleBacked upWhat you have
YesYesA synced passkey, on every device in that manager
YesNoSyncable, not yet saved anywhere else
NoNoDevice-bound. This device or nothing

Read the middle row again, because it is the one that bites. The user has a passkey that is allowed to sync and has not synced yet. They look identical to the synced case in your UI, right up until the device is gone.

Key takeaway

Backup eligibility is permanent and tells you what the credential can ever do. Backup state is a snapshot and tells you what it has done so far. Storing only the second one, at registration, answers the wrong question later.

We store both at registration, and the honest caveat is that we store the backup state as it stood that day. Our login path updates the signature counter and the last-used time, and does not refresh the backup flag. The specification says that flag appears in the data from any ceremony, registration and sign-in alike, so a live value is there to be read on every login if you want one. If you are building recovery prompts off a stored backup flag, know which it is: a snapshot from enrolment day, or something you keep current.

There is a second flag worth the same attention. A passkey ceremony does not always verify the user. It can check that somebody is present, a touch on the authenticator, without checking who. A passkey sign-in that verified the user with a PIN or a biometric counts as both steps on its own, which is why we pass that result through to the login gate rather than assuming it. A ceremony that only proved presence proves the device, and no more.

When a passkey is the wrong choice

An explainer that only lists upsides is a brochure with a dictionary voice, so:

As the only way in. Whatever lets a user back without their passkey becomes the real security boundary of the account. If that is an emailed link, your passkeys are as strong as an inbox. Not a reason to skip them. A reason to stop calling the recovery path an edge case and go look at it.

For accounts people share. A passkey is bound to a credential manager, which belongs to a person. Two contractors and one logistics login do not fit that shape, and the workaround users invent will be worse than whatever you were avoiding.

On machines nobody owns. Shared terminals, kiosks, a borrowed laptop. A passkey there is either unusable or gets enrolled somewhere it should not live.

Before you have settled your auth hostname. Covered above, and it is the expensive one, because it is invisible until the day you move and then it is every user at once.

None of these argue for passwords. They argue for passkeys plus one other method that does not depend on the same device, which is how the sign-in list is built anyway: passkeys sit alongside email codes, magic links and the rest, and a user who cannot reach one reaches another.

Where it sits next to the others

Passkeys are one of the passwordless methods, not the whole category, and the others trade differently. A magic link asks nothing of the device and everything of the inbox: no enrolment, no lost-phone story, and a session handed to whoever is reading the mail. A passkey inverts that. It asks for enrolment on a real device and gives you a credential that phishing does not touch.

The practical answer for most products is both, with the passkey as the fast path and something email-shaped underneath it. If you want the implementation rather than the concept, adding passkeys in Next.js has the decision that actually costs you something, which is the same hostname question as above.

auth
passkeys
webauthn
passwordless

Frequently Asked Questions

Where is a passkey actually stored?

Wherever the user's credential manager puts it: the platform keychain on a phone or laptop, a password manager, or a hardware key. Your server never sees it. All you store is the public half, the credential id, and the handful of fields the ceremony hands you alongside them.

Is a passkey the same thing as a hardware security key?

No, but they overlap. A passkey is a kind of credential; a hardware key is one place that credential can live. A passkey kept on a hardware key is device-bound by definition, because the private half cannot leave the hardware. The same WebAuthn ceremony covers both.

Can one account have more than one passkey?

Yes, and it is the cheapest thing you can do for recovery. BuildBase stores passkeys per user up to a plan limit, and the registration ceremony passes the ones already registered so the browser will not enrol the same authenticator twice. A second passkey on a second device is worth more than any amount of tuning on the first.

Can someone use a passkey on a device that did not create it?

If it is synced, yes, on any device signed into the same credential manager. If it is device-bound, not directly: the usual route is a cross-device sign-in where the computer shows a QR code and the phone holding the passkey approves it. That flow is the fallback, not something to design the happy path around.

Put this into practice

The modules in this post are one console away. 7-day free trial, no credit card.

7-day free trialNo credit card requiredCancel anytime