FAQ

Short answers to the questions that come up before and during adoption.

Adoption

Do I have to remove passwords?question

No, and you probably should not, at least not at first. Passkeys work perfectly well alongside a password form, and running both is how most sites roll them out. See adding to an existing app for the staged path.

What happens if a user loses every device?question

They lose the account, unless you gave them a recovery path. This is the hardest part of going passwordless and no library can solve it for you.

The cheapest mitigation by a wide margin is prompting for a second passkey at signup, on a different device. See account recovery.

Will this work for users who have never heard of passkeys?question

The prompt is the operating system's own, and it says "Touch ID" or "Windows Hello" rather than anything about cryptography. Most users experience it as "sign in with my fingerprint".

The part that confuses people is the first time, when the OS asks whether to save a passkey. A one-line explanation next to your button helps more than any amount of in-prompt wording you cannot control.

Is passkify production ready?question

It implements every check in WebAuthn §7.1 and §7.2, and the test suite drives a virtual authenticator that assembles authenticator data byte by byte and signs with real keys, so each check is exercised against a tampered response rather than just the happy path.

It has not had an external security audit. If that matters for your risk profile, read the security model, read the source, and decide.

Comparisons

How is this different from a hosted auth provider?question

A hosted provider (Clerk, Auth0, WorkOS and similar) runs the identity system for you: sessions, recovery, admin UI, compliance. passkify is a library that verifies WebAuthn ceremonies against your own database, and nothing else.

If you want somebody else to own identity, use a provider. If you already have users, sessions and a login page, and you want passkeys added to them, that is what this is for.

Why not use a WebAuthn library that already exists?question

Existing libraries are good. The differences here are deliberate rather than competitive:

  • Client and server in one package, so the encoding on both sides is written by the same code and cannot disagree.
  • Zero runtime dependencies. The parser sits on attacker-controlled bytes, and every dependency in that path is code you trust without reading.
  • Failure throws. There is no verified: false to accidentally treat as success.
  • An API that refuses to express the account-takeover bug. Registration cannot be pointed at an existing account from a request body.

Behaviour

Why does login() not tell me the username does not exist?question

Because that would make your login form an account-enumeration oracle: anyone could learn which usernames are registered by watching for a 404.

An unknown username gets a normal, well-formed challenge, and the ceremony fails at verification like any other bad attempt. See authentication.

Can one passkey work across my subdomains?question

Yes, if rpID is set to the shared parent domain before the first user registers. rpID: 'acme.com' covers app.acme.com and www.acme.com.

You cannot change it afterwards. Credentials are permanently bound to the rpID they were created under, and changing it orphans every one of them.

Can one account have several passkeys?question

Yes, and it should. Each device registers its own. excludeCredentials stops the same authenticator registering twice, and listCredentials gives you the settings page.

Does passkify support 2FA, where a passkey is the second factor?question

Yes. Run a normal authentication ceremony after your password check, scoped to the account you just verified:

const { options } = await passkeys.startAuthentication({
  userId: userFromPasswordCheck.passkeyHandle,
  userVerification: 'discouraged',   // presence only; the password was factor one
});

What about hardware security keys?question

They work. They register as deviceType: 'singleDevice' because they cannot sync, which matters only for your recovery planning.

Do not set requireBackupEligible unless you intend to turn those users away.

Operations

Do I need Redis?question

No. Any store that can do an atomic fetch-and-delete works, including a plain SQL table with DELETE ... RETURNING.

Redis is a good fit for challenges specifically, because they are short-lived, high-churn, and GETDEL gives the strongest replay guarantee in one round trip. See store adapters.

Can I run this on an edge runtime?question

The browser half, yes. The server half needs node:crypto, so it needs a Node runtime. Cloudflare Workers can do it with nodejs_compat enabled; test that on your target before committing.

How do I migrate credentials from another library?question

You need the raw COSE public key bytes per credential, plus the credential ID, algorithm, counter and the user handle already on the device. rpID must be identical.

If your previous library stored PEM or DER instead of COSE, the conversion is not reversible and users have to re-enrol. Check that before planning the migration. See migrating.

How much does this add to my bundle?question

About 6.5 kB gzipped for passkify/client. The server half never reaches the browser, and importing it from browser code fails at build time rather than shipping something broken.

Still stuck

Troubleshooting covers the specific failures, symptom by symptom. Nearly every remaining case is a disagreement between the configured rpID, the configured origin, and the origin the browser actually reports.