Skip to main content
This guide shows how to write automated API tests that enroll a passkey and complete a passkey challenge against Authsignal. The test process generates a passkey key pair, assembles the WebAuthn structures the server expects, and signs challenges itself. This is ideal when running a browser-based virtual authenticator is slow or impractical. By the end you’ll have two helpers buildPasskeyRegistration and buildPasskeyAuthentication and a complete example function covering the full enroll → challenge → verify loop. The examples are TypeScript + raw HTTP that map to the Authsignal API reference. See the Porting to other languages section at the end if you’re not on Node.

Prerequisites

  • An Authsignal tenant with passkeys enabled
  • Your API secret key (Portal → Settings → API Keys)
  • A test runtime with Node.js 18+ (examples use node:crypto and fetch)
  • The origin your app uses for passkeys (e.g. https://your-test-app.example.com)

How it works

  • Enrollment: generate a key pair, package the public key as a WebAuthn attestation, and register it. Keep the private key in memory.
  • Authentication: when the server issues a challenge, build a WebAuthn assertion and sign it with that same private key. The server verifies the signature against the public key it stored at enrollment.

Authentication & tokens

Every call below is one of two kinds:
  • Server API calls authenticate with your API secret key using HTTP Basic auth.
  • Client API calls authenticate with the short-lived token returned by track, passed as Authorization: Bearer TOKEN.
So the pattern is always: track an action to mint a token, then use that token for the passkey calls. The token enrolling a passkey needs permission to add authenticators, so pass scope: "add:authenticators" when tracking the enrollment action.
authsignal-client.ts
Reference: Track Action.

Setup

The only third-party dependency is a CBOR encoder, used to build the attestation during enrollment. The examples use @levischuck/tiny-cbor, but any spec-compliant CBOR library works. Authentication needs no CBOR at all.
passkey.ts
rpId and origin must match your tenant’s passkey setup, or verification fails. This is the most common cause of isVerified: false. The examples read rpId from the options response so it can’t drift, and you set ORIGIN once to a domain you’ve allow-listed for passkeys.

Enrolling a passkey

Enrollment is three steps: ask for registration options (which include the server’s challenge), build a credential, then submit it. buildPasskeyRegistration generates the key pair, encodes the public key as a COSE_Key, and assembles authenticatorData followed by a fmt: "none" attestation object. It returns the private key so you can sign assertions later.
passkey.ts

Submitting the credential

References: Generate Passkey Registration Options, Verify Passkey Registration.

Authenticating with a passkey

Now complete a challenge with the passkey you just enrolled. Ask for authentication options, build a signed assertion, and submit it for verification. The key idea: the server verifies an ES256 signature over authenticatorData ‖ SHA-256(clientDataJSON) using the public key captured at registration.
passkey.ts
Submitting the assertion:
References: Generate Passkey Authentication Options, Verify Passkey Authentication.

Complete helper example

Everything together. This framework-agnostic sample gives you one function to call from a test(...) in Vitest, Jest, or any runner.
passkey-api.test.ts

Porting to other languages

The Authsignal calls are plain HTTP, so only the credential building is language-specific. Any language can do it with four primitives:
  1. Generate an EC P-256 (secp256r1) key pair and keep the private key in memory.
  2. Encode the public key as a COSE_Key: a CBOR map with kty=2, alg=-7, crv=1, and the x / y coordinates.
  3. Assemble authenticatorData and the attestationObject (fmt: "none") for registration; base64url-encode the parts.
  4. For authentication, ES256-sign authenticatorData ‖ SHA-256(clientDataJSON), DER-encode the signature, and base64url everything.
For the full structure definitions, see the WebAuthn specification and MDN’s Web Authentication API.