Skip to content

Passkey Authentication Flow

Quick reference guide for implementing the invitation-based passkey authentication flow.

Create an invitation token for new user registration.

POST /api/auth/invitations
Authorization: Bearer {admin_token}
Response:
{
"token": "abc123...",
"expiresAt": "2025-12-18T12:00:00Z"
}

Share invitation URL: https://yourdomain.com/register?invite=abc123...


Check if the invitation code is valid before starting registration.

GET /api/auth/invitations/{code}
Response 200: Valid invitation
Response 404: Not found
Response 410: Already used

Start the passkey registration process with the invitation code.

POST /api/auth/passkey/begin?invite={code}
Response:
{
"publicKey": {...},
"sessionId": "d4tc5oh6i4r3m3hbnrhg",
"isRegistration": true
}

Create the passkey credential using the browser’s WebAuthn API.

const credential = await navigator.credentials.create({
publicKey: response.publicKey
});

Submit the credential to complete registration and receive an authentication token.

POST /api/auth/passkey/complete
Headers: { "SessionID": "d4tc5oh6i4r3m3hbnrhg" }
Body: {credential}
Response: { "token": "" }

Start the authentication process (no invitation needed).

POST /api/auth/passkey/begin
Response:
{
"publicKey": {...},
"sessionId": "c9i50ub6ffgg008va1d0",
"isRegistration": false
}

Retrieve the credential using discoverable credentials (no username required).

const credential = await navigator.credentials.get({
publicKey: response.publicKey
});

Submit the credential to authenticate and receive a session token.

POST /api/auth/passkey/complete
Headers: { "SessionID": "c9i50ub6ffgg008va1d0" }
Body: {credential}
Response: { "token": "" }

Session cookie is automatically set by the gateway/middleware.


Use the backend test page for quick testing:

  • Registration: /api/auth/signin?invite={code}
  • Login: /api/auth/signin

To reset test accounts:

  1. Run TRUNCATE admin in your database
  2. Delete the passkey from your authenticator (e.g., 1Password)

  • No email required - Passkey-only accounts don’t require an email address
  • Auto-generated display name - Format: user-{external_id[:8]}
  • Invitation expiry - Invitations expire after 7 days
  • Credential storage - WebAuthn credentials are stored on the device and in NATS KV