WebAuthn Server BuildKit โ a project by Ahsan Mahmood
Framework-independent TypeScript server library that verifies passkey registration and authentication ceremonies for Node.js backends.
A passkey has two halves and almost everything written about it describes the same one.
The browser half is the demonstration. You call an API, a device prompt appears, and something comes back that looks like success. It is not success. What came back is a claim, and a claim is worth exactly what the thing checking it decides to do.
The other half is a server whose job is to disagree.
Verify the signature. Verify that the challenge is one you issued, that it has not been used, that it has not expired and that it was issued for this operation rather than for another. Verify the origin. Verify the relying-party ID hash and the authenticator flags. Verify the signature counter has not gone backwards, because from here that is what a cloned authenticator looks like.
WebAuthn Server BuildKit is that half, written on its own.
It implements the Level 3 registration and authentication ceremonies and stays framework-independent, so it sits behind Express, Fastify, Koa, Next.js or anything else running on Node.js. Storage is an adapter, so credentials and challenges live in PostgreSQL, MongoDB, Redis or whatever your product already runs. That choice stays yours.
Then the part I would rather be honest about than impressive.
Attestation is where libraries of this kind overclaim. A statement is verified per format, and it is reported as trusted only when it chains to a trust anchor you supplied. With no anchor there is no trust claim: the library says the statement verified structurally, and stops.
That one decision separates a security library from a security feeling.
Sessions are encrypted with AES-256-GCM and can be created, validated, refreshed and revoked. The algorithm allowlist is explicit, and a credential's algorithm is pinned at authentication so it cannot be quietly substituted later. Everything is secure by default, with opt-outs you have to write down deliberately.
Errors are typed and machine-readable, each carrying a suggested HTTP status, because a verification failure your route handler cannot classify turns into a 500 and a support ticket.
What it cost was scope discipline.
One runtime dependency, and not zero. Parsing CBOR by hand is an efficient way to write a security bug nobody notices, so that one stayed and everything else was refused.
311 tests hold the ceremonies, and the ones that matter are the failing cases, since a verifier nobody has watched refuse anything is not yet a verifier. Dual ESM and CommonJS builds with type declarations mean it drops into a backend that already has opinions about modules.
There is a limit written into the package rather than discovered in production.
This is a relying-party library. It does not manage your users, own your session store, or decide your recovery policy, and a passkey system without a recovery policy is a support queue with a date on it.
It pairs with capacitor-biometric-authentication on the client side. The two were written against each other rather than against a specification read separately twice, which is the only reason their assumptions line up.
There is no iOS release behind any of this, because there is no Apple Developer account here. This is server code.
MIT, on npm as webauthn-server-buildkit, with the source at github.com/aoneahsan/webauthn-server-buildkit.
Read the verification path before installing it. This is the layer where nearly right and right look identical from the outside, and the difference only shows up on the day somebody is looking for it.
What it does, and what that costs to build
- Server-side WebAuthn registration and authentication ceremony verification
- Per-format attestation verification (packed, fido-u2f, android-key, tpm, android-safetynet, apple) with honest trust-anchoring
- Single-use, expiring, operation-scoped challenge enforcement
- Algorithm allowlist plus credential-algorithm pinning at authentication
- AES-256-GCM encrypted session create, validate, refresh, and revoke
- Storage-agnostic adapter system for MongoDB, PostgreSQL, Redis, or any database
- Framework-independent integration with Express, Fastify, Koa, and Next.js
- Typed, machine-readable error classes with suggested HTTP status codes
Built with
- TypeScript
- Node.js
- cbor-x
- WebAuthn Level 3
- FIDO2
- COSE
- CBOR
- X.509/ASN.1
- AES-256-GCM
- tsup
- ESLint
- Yarn
Worth knowing
- webauthn
- passkeys
- fido2
- typescript
- nodejs
- authentication
- biometrics
- npm-package
Who built WebAuthn Server BuildKit?
Ahsan Mahmood wrote the verification code, the attestation handling and the session layer. It is on npm as webauthn-server-buildkit under MIT, with the source at github.com/aoneahsan/webauthn-server-buildkit. It exists because the client-side biometric package he had already published would have been an unfinished feature without it: a client can run a ceremony, and only a server can decide whether the result means anything at all. One author on both sides is why the two agree about the shape of what crosses between them.
What exactly does it verify?
The signature, the challenge, the origin, the relying-party ID hash, the authenticator flags and the signature counter, across both registration and authentication ceremonies at WebAuthn Level 3. The challenge checks are the ones most often reduced to a formality: it has to be one your server issued, unused, unexpired and scoped to this operation rather than to another. The counter check earns its place for a different reason. A counter that has gone backwards is what a cloned authenticator looks like from the server's side of the connection.
Does WebAuthn Server BuildKit run on iPhone?
It does not run on a phone at all, because it is server code executing on Node.js behind your API. The device question belongs to whichever client talks to it, and the browsers and platforms that implement WebAuthn are what decide it. What I will not claim in either direction is an Apple platform: there is no Apple Developer account behind this work and nothing I build has shipped to the App Store, so read anything Apple-shaped here as untested rather than supported.
Which database does it need?
Whichever one you already run. Storage is a pluggable adapter, so credentials and challenges can sit in PostgreSQL, MongoDB, Redis or anything else you can write an adapter against, and the library holds no opinion about your schema beyond what it must read back. That is deliberate. A passkey library that dictates a datastore forces an operational decision having nothing to do with authentication. The one runtime dependency it does carry handles CBOR parsing, which is not a thing to hand-roll.
What does honest trust-anchoring mean?
It means a statement is reported as trusted only when it chains to a trust anchor you supplied. With no anchor, the library tells you the attestation verified structurally and nothing further, rather than presenting an unanchored verification as proof of where a device came from. Most products do not need attestation trust at all, and being told so plainly is more useful than a green tick. The distinction is small in code and it is the entire difference between a security library and a comfortable feeling.
https://aoneahsan.com/projects/com.aoneahsan.webauthnserverbuildkit