Table of Contents

API : Passkey Authenticate

Introduction

Second step of a passkey login: verifies the browser's WebAuthn assertion and, on success, issues a token exactly as /auth/login does. A user-verified passkey counts as two factors, so SMS and authenticator (TOTP) validation are skipped for this login. Unauthenticated.

See webauthn-authenticate-options for the preceding step and webauthn for the relying-party model and the full error-code table.

Request

URL https://api.telecomx.dk/auth/webauthn/authenticate
Method POST
Header Origin String Required. Must be the same origin the challenge was minted for
Body JSON object
challengeId String The id returned by webauthn-authenticate-options, exactly 43 characters
response Object The AuthenticationResponseJSON produced by the browser's navigator.credentials.get() (e.g. via startAuthentication())
cookie Boolean Optional. True if the server should also set the token as a cookie, default false
persist Boolean Optional. True for a token that does not expire until logout, default false

cookie and persist must be JSON booleans (true/false, not the strings “true”/“false”). Unlike /auth/login, which coerces query/body strings with a bool() helper, this endpoint's body is Zod-validated as z.boolean().optional() - a string value fails validation instead of being coerced.

Body example

{
  challengeId: 'Zm9vYmFyYmF6cXV4Y29ycmVjdGhvcnNlYmF0dGVyeXN0YXBsZQ',
  response: {
    id: 'AXCxIVI...',
    rawId: 'AXCxIVI...',
    type: 'public-key',
    response: {
      clientDataJSON: '...',
      authenticatorData: '...',
      signature: '...',
      userHandle: '...'
    }
  },
  cookie: false,
  persist: false
}

Response

Identical to login's response - the same token, expiresInMinutes, employee and customer fields, employeeFeatures, etc. See that page rather than this one for the full field table; nothing in the shape changes for a passkey login.

The LoginLog entry this login writes carries method: 'PASSKEY' - see loginlog.

Errors

Error code Message Description
401 origin_invalid Origin header is missing, not https (outside development), or not a known/active portal
401 challenge_invalid Challenge id is malformed, expired, already used, or was minted for a different origin
401 credential_unknown response.id does not match any registered credential for this relying party
401 verification_failed The signature or client data could not be verified
401 user_verification_required The authenticator did not perform user verification
401 account_disabled Employee state is not ENABLED, or access level is NO_LOGIN, or the customer is not ACTIVE
401 m2m_only The employee is m2mOnly and must authenticate as M2M instead
404 customer The employee's customer document could not be found
401 ipaddress_invalid The employee's ipRestriction excludes the caller's IP
429 (empty) Rate limit exceeded on the login scope, keyed by the caller's IP

An unknown credential, a failed verification, or a missing employee record each also register a brute-force attempt against the caller's IP, exactly as a failed password login does.