@lacspace/webauthn
v1.2.1
Published
Passkeys / biometric (FaceID, fingerprint, security keys) — browser ceremony helpers + server challenge, options and assertion verification (ES256/RS256/Ed25519) over Web Crypto, with passkey-sync (backup) flags, AAGUID, UV/resident-key policy and transpo
Maintainers
Readme
@lacspace/webauthn
Passkeys & biometric (FaceID, fingerprint, security keys) via WebAuthn.
1.2.1 — expectedOrigin is normalised
expectedOrigin: "https://example.com/" — a trailing slash from an APP_URL,
or a capitalised host — rejected every sign-in with "origin mismatch". The
configured origin is now compared as a canonical origin (scheme, host, port),
so those forms match. https://evil.com, http://, subdomains and
look-alike hosts still do not.
Everything for passwordless / biometric auth in one small package: browser ceremony helpers, server challenge & options builders, and real assertion verification (ES256/RS256/Ed25519) over Web Crypto — including the fiddly ES256 DER→P1363 conversion and a compact CBOR/COSE parser that extracts the public key at registration.
New in 1.2.0 — additive & fully backward compatible: Ed25519 (COSE −8) verification alongside ES256/RS256 · passkey-sync flags on every verified result (
backupEligibleBE,backupStateBS,userPresentUP,userVerifiedUV) plus theaaguidand echoedtransports· UV / resident-key policy (requireUserVerification,requireResidentKey) · richerallow/excludeCredentialsdescriptors carryingtransports· new helpersreadAuthenticatorData,parseAuthenticatorFlags,formatAaguid.
- 👆 Browser:
startRegistration/startAuthentication,isPlatformAuthenticatorAvailable - 🖥️ Server:
generateChallenge,generateRegistrationOptions,generateAuthenticationOptions - ✅ Server verify:
verifyRegistration(extracts the public key),verifyAuthentication(signature + rpId + origin + challenge + counter) - ⚡ Zero dependencies · 🌍 isomorphic (Web Crypto) · fully typed
Scope: performs origin / rpId / challenge / signature / sign-counter checks — the checks that matter for nearly every app — with
"none"attestation. It does not verify the attestation-statement trust chain (device provenance); if you need enterprise attestation, pair it with a specialist verifier.
Install
npm install @lacspace/webauthnRegister a passkey
// --- server: create options ---
import { generateRegistrationOptions, generateChallenge } from "@lacspace/webauthn";
const challenge = generateChallenge(); // store in the session
const options = generateRegistrationOptions({ rpName: "Lacspace", rpID: "lacspace.com", userID, userName, challenge });
// --- browser ---
import { startRegistration } from "@lacspace/webauthn";
const response = await startRegistration(options); // FaceID / fingerprint prompt → JSON
// --- server: verify + store ---
import { verifyRegistration } from "@lacspace/webauthn";
const { credentialId, publicKey, algorithm, counter } = await verifyRegistration({
attestationObject: response.attestationObject,
clientDataJSON: response.clientDataJSON,
expectedChallenge: challenge, expectedOrigin: "https://lacspace.com", expectedRPID: "lacspace.com",
});
// store { credentialId, publicKey (JWK), algorithm, counter } against the userSign in with a passkey
// --- server ---
import { generateAuthenticationOptions } from "@lacspace/webauthn";
const challenge = generateChallenge();
const options = generateAuthenticationOptions({ rpID: "lacspace.com", allowCredentials: [credentialId], challenge });
// --- browser ---
import { startAuthentication } from "@lacspace/webauthn";
const response = await startAuthentication(options);
// --- server: verify ---
import { verifyAuthentication } from "@lacspace/webauthn";
const { verified, newCounter, userVerified } = await verifyAuthentication({
authenticatorData: response.authenticatorData,
clientDataJSON: response.clientDataJSON,
signature: response.signature,
publicKey, algorithm, counter, // from storage
expectedChallenge: challenge, expectedOrigin: "https://lacspace.com", expectedRPID: "lacspace.com",
requireUserVerification: true, // reject unless biometric/PIN was performed (UV flag)
});
if (verified) { /* update stored counter = newCounter, log the user in */ }User verification (UV). Pass
requireUserVerification: truetoverifyAuthentication(orverifyRegistration) to reject assertions where the authenticator did not verify the user via biometric or PIN — enforce this when a passkey is your second factor or your sole credential. When the option is omitted, behaviour is unchanged (only User-Present is required). Both verifiers also returnuserVerifiedso you can record or branch on it.
Passkey-sync flags, AAGUID & policy (1.2.0)
Every verified result now surfaces the authenticator-data flags so you can be sync-aware and, if you like, enforce policy:
const r = await verifyRegistration({ /* …, */ transports: response.transports });
// r.backupEligible → credential can sync across devices (a "passkey")
// r.backupState → it is currently backed up / synced
// r.userPresent, r.userVerified, r.aaguid (authenticator model), r.transports (store these)
const a = await verifyAuthentication({ /* …, */
requireUserVerification: true, // reject unless UV=1
});
// a.userPresent / a.userVerified / a.backupEligible / a.backupState
// Registration policy: require a discoverable (resident) key using the browser's credProps.rk
await verifyRegistration({ /* …, */ requireResidentKey: true, residentKey: response.residentKey });Ed25519. verifyRegistration extracts OKP/Ed25519 keys and verifyAuthentication verifies them (algorithm: "Ed25519"), joining the existing "ES256" / "RS256".
Transports. startRegistration returns transports (and residentKey from credProps); store them and pass credential descriptors — allowCredentials: [{ id, transports }] — to generateAuthenticationOptions (a bare string ID is still accepted).
Read any authenticatorData without verifying:
import { readAuthenticatorData } from "@lacspace/webauthn";
const info = readAuthenticatorData(response.authenticatorData);
// { rpIdHash, flags: { userPresent, userVerified, backupEligible, backupState, … }, signCount, aaguid?, credentialId? }API
| Export | Where | Description |
| --- | --- | --- |
| isWebAuthnSupported / isPlatformAuthenticatorAvailable | browser | feature detection |
| startRegistration / startAuthentication | browser | run the ceremony (registration also returns transports + residentKey) |
| generateChallenge | server | random challenge |
| generateRegistrationOptions / generateAuthenticationOptions | server | build options JSON (residentKey, attestation, transports descriptors) |
| verifyRegistration / verifyAuthentication | server | verify + extract key (ES256/RS256/Ed25519), flags, AAGUID, UV / resident-key policy |
| readAuthenticatorData | any | parse a raw authenticatorData buffer into flags / signCount / AAGUID / credentialId |
| parseAuthenticatorFlags / formatAaguid | any | decode the flag byte · format a 16-byte AAGUID |
The Lacspace Security Kit
| Package | For |
| --- | --- |
| @lacspace/crypto | AES encryption & hashing |
| @lacspace/password | Password hashing |
| @lacspace/jwt | JWTs & tokens |
| @lacspace/apikey | API keys |
| @lacspace/otp | TOTP/HOTP 2FA |
| @lacspace/webauthn | Passkeys / biometric (this package) |
| @lacspace/mfa | 2FA/3FA orchestration |
| @lacspace/lock | Account lockout |
| @lacspace/headers | Secure headers / CSP |
| @lacspace/redact | Log redaction |
Licensing
This package is free under the Lacspace Free Licence — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
Not every Lacspace package is free. We also offer Commercial (paid), Client-specific, and Private (proprietary) packages under separate terms. See the full Lacspace Licence Centre.
The Lacspace Developer Platform
@lacspace/webauthn is part of 80+ zero-dependency, isomorphic TypeScript packages — one standard library for the modern web. Explore the ecosystem:
- 📦 This package, documented — https://developer.lacspace.com/packages/webauthn
- 🗂️ All 80+ packages — https://developer.lacspace.com/packages
- 🧭 Developer handbook — guides & runnable recipes — https://developer.lacspace.com/handbook
- 🧪 Live playground — run any package in your browser — https://developer.lacspace.com/playground
- 🖥️ Finished app templates — https://templates.lacspace.com
- 🚀 Scaffold a full app —
npm create lacspace-app@latest
Free under the Lacspace Free Licence — a permissive, free-to-use licence.
