Qeet Docs
Authentication

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.

POST/v1/passkeys/register/beginGet creation options
POST/v1/passkeys/register/finishStore the credential

Sign 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.

POST/v1/passkeys/login/beginGet request options
POST/v1/passkeys/login/finishAssert → token pair

Browser ceremony

register (browser)
TypeScript
// 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) });
login (browser)
TypeScript
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 = TokenPair

HTTPS 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.

GET/v1/passkeysList a user's passkeys
DELETE/v1/passkeys/{id}Revoke a passkey

Passkeys 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.

On this page