Table of Contents

API : Passkey Enroll

Introduction

Registers a passkey for an employee who holds a recovery enrollment token instead of a session - the path used when someone has lost every passkey and cannot log in at all. Both endpoints are unauthenticated (no Authorization header); the token itself stands in for the session.

See webauthn for how an admin issues the token (POST /employee/:id/webauthn/enroll, documented on webauthn) and the recovery procedure end to end.

Token semantics

EmployeeWebauthnEnrollTokens keyed on the

  employee's own ''_id'' - one pending token per employee.
* Valid for **one hour**. This is enforced in application code (a ''created > cutoff''
  comparison) independently of the collection's TTL index, so an expired token cannot be used
  even if ''create-webauthn-indexes.js'' was never run to create that index, or MongoDB's
  once-a-minute TTL sweep has not yet run.
* **Single-use**, consumed atomically by ''POST /auth/webauthn/enroll'' via ''findOneAndDelete''
  - two concurrent requests racing the same token can never both succeed.
* The token is consumed **before** the challenge is checked and before the WebAuthn response is
  verified. This is deliberate: if verification then fails, the token stays spent rather than
  reusable, which is the correct trade-off for a single-use recovery credential - but it does
  mean a failed enroll attempt (wrong relying party, cancelled prompt, expired challenge) burns
  the token, and the admin must issue a fresh one.
* An unknown or expired token also registers a brute-force attempt against the caller's IP.
* Both endpoints are rate-limited on the ''login'' scope, keyed by a hash of the token itself
  (not the caller's IP).
* Both endpoints re-check that the employee and their customer may still log in (state
  ''ENABLED'', access level not ''NO_LOGIN'', not ''m2mOnly'', customer ''ACTIVE'') - the same
  gate a password or passkey login applies.

Enroll: options

Leaves the token in place (does not consume it), so the same token can still be used by POST /auth/webauthn/enroll afterwards.

URL https://api.telecomx.dk/auth/webauthn/enroll/options
Method POST
Header Origin String Required. Decides which relying party the new passkey is created for
Body JSON object
token String The enrollment token, exactly 43 characters

Response

Property Type Description
challengeId String Pass unchanged to the enroll step below
options Object PublicKeyCredentialCreationOptions, built the same way as webauthn-register-options - residentKey: “required”, userVerification: “required”, attestationType: “none”, excludeCredentials listing the employee's existing credentials for this relying party

Errors

Error code Message Description
401 origin_invalid Origin header is missing, not https (outside development), or not a known/active portal
401 enroll_token_invalid Token is unknown, expired, or the account/customer may not log in
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
404 customer The employee's customer document could not be found
429 (empty) Rate limit exceeded on the login scope, keyed by the token

Enroll

Consumes the token, verifies the response, and stores the credential. Sends the employee the same “a passkey was added” notification e-mail as webauthn-register.

URL https://api.telecomx.dk/auth/webauthn/enroll
Method POST
Header Origin String Required. Must be the same origin the challenge was minted for
Body JSON object
token String The enrollment token, exactly 43 characters
challengeId String The id returned by the enroll/options call above, exactly 43 characters
response Object The RegistrationResponseJSON from the browser
name String Optional, 1-60 characters. Defaults the same way as webauthn-register

Response

{ success: true }

Unlike webauthn-register, this does not return the stored credential in the response body.

Errors

Error code Message Description
401 origin_invalid Origin header is missing, not https (outside development), or not a known/active portal
401 enroll_token_invalid Token is unknown, already consumed, expired, or the account/customer may not log in
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
404 customer The employee's customer document could not be found
401 challenge_invalid Challenge is malformed, expired, already used, or its stored rpId/origin/employee does not match this request
401 verification_failed The attestation could not be verified
409 credential This credential id is already registered - to anyone
429 (empty) Rate limit exceeded on the login scope, keyed by the token