Authentication
Authenticate users with ZK proofs. Handle success, failure, and debug mode.
Authentication in AuthenSee works through a hosted challenge-response protocol backed by zero-knowledge proofs. The user completes the passkey and factor ceremony in the AuthenSee-hosted runtime; your backend receives only a one-time auth-result code and exchanges it for provider-scoped claims.
This is the on-device engine, not a third-party integration path.
authenticate()runs the WebAuthn passkey ceremony, which only succeeds on AuthenSee's own origin (the hosted flow and first-party apps). To authenticate users from your own app, launch the hosted popup — see the embed guide. This page documents how authentication works inside that flow.
Every enrollment is registered against one scheme, and a successful authentication attests to that scheme's factors in a single ZK proof:
| Scheme | Factors | Selected by provider policy |
|---|---|---|
passkey_question_v1 | Image points + passkey | passkey_and_image_points (default) |
behavior_passkey_v1 | Motion gesture + passkey | passkey_and_behavior |
passkey_only_v1 | Passkey only | passkey_only, or opportunistically alongside any other scheme |
agent_keypair_v1 | Raw ECDSA-P256 keypair (no WebAuthn) | Agent personas only |
The auth server resolves the scheme from the challenge and derives factorsVerified from it — clients never send factorType or circuitType to /v1/verify. A provider's factor combination determines which scheme is allowed at login; see Provider policy for what happens when a user's enrolled scheme falls outside the provider's current policy.
Authentication with hosted flow
On your backend, exchange the one-time result code returned to your callback URL:
The browser passkey ceremony is handled by the hosted runtime. Provider code
does not receive personaId, challengeId, commitments, public inputs, or
session tokens.
The authentication flow
The walkthrough below uses passkey_question_v1 (image points + passkey) as the concrete example; behavior_passkey_v1 and passkey_only_v1 follow the same shape with their own factor (or none) in place of the answer, and agent_keypair_v1 skips the WebAuthn ceremony entirely in favor of a raw ECDSA-P256 signature.
When authenticate() is called, the SDK executes:
-
Look up the on-device enrolment witness. The witness lives in sessionStorage and contains the image-points Merkle path + salts + passkey material along with the
enrollmentIdreturned by the server at enrol time. -
Request a challenge —
POST /v1/challengeswith{ personaId, enrollmentId }. The server returns{ challengeId, nonce, challengeBytes (32 random bytes), publicInputLayout: { authCommitmentIndex, challengeFieldIndex, nullifierIndices, totalLength }, schemeId, factors }. -
Collect the answer + run the WebAuthn ceremony. The SDK invokes the
passkey.signcallback with the server'schallengeBytes; the callback runsnavigator.credentials.get(...)and returns the parsed assertion. -
Build the witness — 100 flat public inputs (
auth_commitment,challenge_field,challenge_bytes[32],action_hash,expected_rp_id_hash[32],expected_origin_hash[32],auth_nullifier) plus the private witness (clientDataJSON, authenticatorData, signature, pubkey, Merkle path, salts).passkey_commitmentis no longer public — the circuit recomputes it internally from the private witness. -
Generate ZK proof — On-device via WasmProver (bb.js UltraHonk). The circuit asserts:
- ECDSA-P256 verifies against the passkey pubkey
- clientDataJSON binds the server's
challengeBytesand the expected origin - Merkle path lifts the answer leaf to the enrolled
question_root - The recomputed
passkey_commitment+question_roottogether hash to the enrolledauth_commitment
Wrong answer or wrong passkey → assertion failure inside Barretenberg →
INVALID_PROOFsurfaced locally, no server round-trip. -
Submit proof —
POST /v1/verifywith{ challengeId, personaId, proof, publicInputs, nullifiers }. The SDK extractsnullifiersusing the indices the server published inchallenge.publicInputLayout.nullifierIndices— clients never hardcode public-input positions. -
Server verification — Resolves the scheme from the challenge's stored
schemeId, looks up the verifier circuit + expected layout from the registry, asserts strict public-input bindings (forpasskey_question_v1:auth_commitmentat 0,challenge_fieldat 1,auth_nullifierat 99 — each scheme has its own layout), verifies the proof against the scheme's pinned VK, atomically claimsauth_nullifier, and issues the JWT withfactorsVerifiedderived from the scheme registry.
The AuthResult type
Handling errors
When authentication fails, the error field contains a structured error code:
Error codes reference
| Code | Description |
|---|---|
INVALID_PROOF | ZK proof verification failed on the server |
FACTOR_NOT_ENROLLED | Attempted to authenticate with a factor that is not enrolled |
CHALLENGE_EXPIRED | Authentication challenge has expired (server-side TTL, default 5 minutes) |
MERKLE_ROOT_STALE | Proof references an outdated Merkle root (factor was updated) |
NETWORK_ERROR | Network request failed (proof generation still works offline) |
USER_CANCELLED | User cancelled the authentication flow |
INCORRECT_ANSWER | The image-points answer didn't match what was enrolled (surfaced locally, before any server round-trip) |
INCORRECT_MOTION | The motion gesture didn't match the enrolled template |
PROVER_INIT_FAILED | The on-device WASM prover failed to initialize |
PROOF_GENERATION_FAILED | Proof generation failed for a reason other than an incorrect factor |
FACTOR_ERROR | Generic factor-collection error (e.g. malformed input) |
PERSONA_ALREADY_ENROLLED | Bootstrap enrollment attempted for a persona that already has a current enrollment |
Debugging
Provider backends should log only provider-scoped session ids, auth-result ids, and error codes. Do not log callback codes, provider secret keys, or issued tokens. Runtime proof-generation diagnostics are owned by the hosted flow.
Offline support
Proof generation works entirely offline. The network is only required for:
- Challenge retrieval — Fetching a fresh nonce from the server before authentication.
- Proof submission — Sending the generated proof to the server for verification.
If the device is offline, the SDK queues the proof and submits it when connectivity is restored.
Performance targets
| Metric | Target |
|---|---|
| End-to-end auth time | P50: 5 seconds, P95: 10 seconds |
| Server-side verification | P99: < 500ms |
Next
- Enrollment — what the persona proved control of, and how it got there
- Events — lifecycle events for progress UI and analytics
- Verification reference — every server-side check on
/v1/verify