Passkeys / WebAuthn
Phishing-resistant passwordless sign-in with WebAuthn passkeys — registration, login, and credential management.
A passkey is a WebAuthn credential bound to your origin and stored on the user's
device or synced credential manager. Qeet ID implements passkeys with
go-webauthn: passwordless registration
and login, plus credential management.
This is fully implemented
Earlier docs claimed passkeys returned 501 Not Implemented. That is no
longer true — registration and passwordless login both work today.
How it works
WebAuthn is a two-step ceremony. The server issues a challenge (begin), the browser
signs it with the authenticator, and the server verifies the response (finish).
Register a passkey (authenticated user)
The user must be signed in. register/begin returns
publicKeyCredentialCreationOptions; pass them to
navigator.credentials.create() and post the result to register/finish.
/v1/passkeys/register/beginGet creation options/v1/passkeys/register/finishStore the credentialSign in with a passkey (passwordless)
These endpoints are unauthenticated — they are the authentication. login/begin
returns publicKeyCredentialRequestOptions; pass them to
navigator.credentials.get() and post the assertion to login/finish, which returns
a token pair.
/v1/passkeys/login/beginGet request options/v1/passkeys/login/finishAssert → token pairBrowser ceremony
// 1. Ask Qeet ID for creation options
const options = await fetch("/api/passkeys/register/begin", { method: "POST" }).then((r) => r.json());
// 2. Create the credential with the platform authenticator
const cred = await navigator.credentials.create({ publicKey: decode(options) });
// 3. Send the attestation back to finish registration
await fetch("/api/passkeys/register/finish", { method: "POST", body: encode(cred) });const options = await fetch("/api/passkeys/login/begin", { method: "POST" }).then((r) => r.json());
const assertion = await navigator.credentials.get({ publicKey: decode(options) });
const tokens = await fetch("/api/passkeys/login/finish", { method: "POST", body: encode(assertion) }).then((r) => r.json());
// tokens = TokenPairHTTPS required
WebAuthn requires a secure context. Passkeys silently fail on plain HTTP origins;
localhost is exempt for development.
Manage credentials
Users can list and revoke their registered passkeys.
/v1/passkeysList a user's passkeys/v1/passkeys/{id}Revoke a passkeyPasskeys as a second factor
The same registered passkeys can be reused as a WebAuthn second factor during step-up MFA — see MFA → WebAuthn as a second factor.