webauthn wraps the passkey protocol in a small, opinionated package
Webauthn / passkeys helper library to make your life easier. Client side, server side and demo included.
At a glance
- What is it?
- The @passwordless-id/webauthn package is a dependency free TypeScript wrapper around WebAuthn that ships the client and the server side in one npm package, defaults to platform authenticators with preferred user verification, and runs on Node 19 and later, on Cloudflare Workers and on any platform with WebCrypto.
- Who is it for?
- webauthn fits a team implementing passkeys for a web application that already has a server and a user model, and who want the ceremony handled by a small library they can read rather than by a framework they have to adopt.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the package actually is
The library describes itself as one that greatly simplifies the usage of passkeys by invoking the WebAuthn protocol more conveniently, and it lists its own qualities: open source, opinionated, dependency-free and minimalistic.
That combination is unusual enough to be worth unpacking. Opinionated means the library makes decisions for you, such as which authenticator to prefer and what user verification to request, and the README is explicit about those defaults rather than leaving them to be discovered. Dependency-free means the runtime dependency list is empty, which for a security library is a meaningful property, because every dependency is code that runs in the path between a user's authenticator and a server's decision. Minimalistic means the surface is small enough to read in an afternoon.
The package contains both halves of the protocol. The base package holds the client and the server modules, and you import the one you need:
npm install @passwordless-id/webauthnimport {client} from '@passwordless-id/webauthn'
import {server} from '@passwordless-id/webauthn'The README notes that the brackets in the import are important, which is a small hint about how the package is structured internally: the client and the server are separate submodules rather than one object with everything on it.
For the audience, this is a library for a team that has decided to implement passkeys itself, in a stack where TypeScript runs on both ends. The documentation site is webauthn.passwordless.id, and the demos are plain HTML and JavaScript that you can open in a browser and read.
Registration and authentication, three calls
The getting started section shows the most straightforward case, and it is short enough to quote in full.
Registration asks the client to create a credential, with a challenge the server generated and a user identifier:
import {client} from '@passwordless-id/webauthn'
await client.register({
challenge: 'a random base64url encoded buffer from the server',
user: 'John Doe'
})Authentication asks the client to produce an assertion, and the only required argument is the challenge:
import {client} from '@passwordless-id/webauthn'
await client.authenticate({
challenge: 'a random base64url encoded buffer from the server'
})The server then verifies what came back, against what it expected:
import {server} from '@passwordless-id/webauthn'
await server.verifyRegistration(registration, expected)
await server.verifyAuthentication(authentication, expected)What is worth reading carefully are the defaults, because they are the opinionated part. By default registration happens on any authenticator, local or roaming, with preferred user verification. By default authentication triggers the native passkey selection dialog for any authenticator, again with preferred user verification.
Preferred rather than required user verification is a deliberate relaxation. The stricter setting would demand that the authenticator itself verify the user, which security keys without a biometric sensor cannot do, and a library that insisted on it would break the security key case. The trade is that a user may be asked for a PIN or a biometric depending on the device, and the application has to accept a verification outcome it did not strictly require.
Version 2 changed the defaults, and the changelog says how
The README has a section explaining that version 2 introduced breaking changes, different default behaviour and a different intermediate format, and it describes the change as a complete overhaul. For the why, it points at a blog post on the project site.
The summary of the core changes is worth having in front of you, because each one alters what your server will receive:
The platform authenticator is used by default, so the authenticator selection pops up by default. The authenticatorType field was removed in favour of hints. The user verification default moved from required to preferred. The timeout went from one minute to no timeout at all. The response format changed. Transports became part of allowCredentials.
The removal of the timeout is the one to think hardest about. A client that can wait forever for a user to complete an authentication ceremony needs a plan for the case where the user never does, and that plan now belongs to the application rather than to the library. The same applies to the shift in user verification, since the value the authenticator returns is no longer guaranteed to be what a strict application might have expected.
For anyone still on the 1.x line, the documentation for the legacy version is kept at a separate address, so a migration does not mean losing access to the old behaviour.
One package, many runtimes, and the Node 19 floor
The runtime story is where a browser-oriented library becomes interesting for backend teams.
On the server side, the supported list is NodeJS 19 and later, Cloudflare Workers, all other JavaScript platforms that support the WebCrypto API, and all other server side libraries in other programming languages, as long as they use the default payload generated by the WebAuthn API. That last point is the important one. Because the client produces a standard payload, a Go or Rust or Java server does not have to use this library to verify it, as long as it consumes the default format.
The Node 19 floor has a specific reason. Earlier Node versions did not have WebCrypto globally available, which made a universal build impossible. The CommonJS variant is available for old Node stacks and is imported with require, but the README says its usage is discouraged in favour of the default ES modules, and the package manifest is marked as an ES module with a type of module and a module field pointing at the ESM build.
For browsers, the package can be loaded from a CDN inside a module script:
<script type="module">
import { client } from 'https://cdn.jsdelivr.net/npm/@passwordless-id/webauthn';
</script>The client side support list covers Chrome, Edge, Firefox, Safari and Opera, with old Internet Explorer 11 excluded. The README frames it more precisely: it supports all browsers that implement the WebAuthn API including the getPublicKey() and getPublicKeyAlgorithm() methods, and the outcome may also depend on the password manager in use, as long as that is specification compliant.
The gap: native iOS and macOS clients
There is one compatibility hole in the server side, and the README states it directly. Native iOS and macOS clients written in Swift are apparently not compatible with the server-side part of this library, because they do not provide the publicKey and publicKeyAlgorithm properties during registration in the first place, and the library links the issue where this is discussed.
The cause is worth understanding rather than just noting. The WebAuthn API in a browser returns the credential public key and its algorithm as part of the registration result, and the server side of this library expects them. A native platform implementation that follows the specification for its own purposes may simply not return those two values, so the data the server needs to store the credential is not present in the response.
This is a real limit for anyone planning a passkey rollout that spans a website and native apps on the same account, and it is the kind of issue that only shows up after the credential table is designed. The fix is on the client side, in providing those properties, and the alternative is a server implementation that can obtain the public key another way.
Everything else in the runtime list is favourable. A Cloudflare Worker or a Node server can run the same verification code, and a service written in another language can verify the default payload without this library at all.
How the package is built, and what it ships
The package manifest is a good summary of what the library gives you.
It publishes only the dist directory, and it offers several entry points. The module field points at an ESM build under dist/esm, the types field at the matching declaration file, the main field at a CommonJS bundle built for Node, and the unpkg and jsdelivr fields at a minified browser bundle. The build script chains the module build, the Node bundle, the browser bundle and the demos, and each bundler step is esbuild with a banner carrying the package name and version, except the module build which is tsc.
The demos are copied into the docs directory as part of the build, which is why the published documentation site can serve the browser bundle and the demo pages from the same repository.
On the tooling side, the tests run under Jest with a jsdom environment and a custom resolver, the type checking is TypeScript, and the documentation is MkDocs, with a dev script that serves it. The repository carries a build.sh, a mkdocs.yml, a docs-theme directory and a CNAME, which is the standard set of files for a project that publishes a documentation site alongside its package.
There is one packaging change worth knowing about, because it will show up in your own tooling. In the latest 2.4 release, the browser field was removed from package.json, because of the issues various tools have with interpreting that field, and the README links the issue for the details.
The licence is MIT, and the releases are steady: 2.3.5 on 2026-02-15, 2.4.0 on 2026-05-15 and 2.4.1 on 2026-10-06, with the last push on the same day as that release.
Editorial conclusion
webauthn fits a team implementing passkeys for a web application that already has a server and a user model, and who want the ceremony handled by a small library they can read rather than by a framework they have to adopt. It is the wrong choice if you need a complete identity product, since the library is a tool for the WebAuthn ceremony and leaves account recovery, e-mail verification and multi-authenticator registration to you, and it is the wrong choice for native iOS or macOS clients that do not send the publicKey and publicKeyAlgorithm fields the server side expects. Before adopting it, read the version 2 changelog notes, because the defaults changed, and check the browser and runtime list against the clients you actually need to support.
Frequently asked questions
What does the @passwordless-id/webauthn package do?
It is a small, opinionated, dependency-free wrapper around the WebAuthn protocol that makes passkeys easier to use, shipping the client and the server side in one package. The client module registers and authenticates, and the server module verifies the results.
How do I register a passkey with webauthn?
Call client.register with a challenge from the server and a user identifier. By default this registers on any authenticator, local or roaming, with preferred user verification, and the server then calls server.verifyRegistration with the response and what it expected.
What are the default user verification and timeout settings in version 2?
User verification moved from required to preferred, and the timeout went from one minute to no timeout. The platform authenticator is used by default, so authenticator selection pops up, and authenticatorType was removed in favour of hints.
Which runtimes can run the webauthn server side?
NodeJS 19 and later, Cloudflare Workers, and any other JavaScript platform supporting the WebCrypto API. Server libraries in other languages can also verify, as long as they consume the default payload produced by the WebAuthn API.
Can native iOS and macOS apps use this library?
Not on the server side, according to the README. Native Swift clients do not provide the publicKey and publicKeyAlgorithm properties during registration, so they are not compatible with the library's server-side verification, and the linked issue discusses the detail.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/passwordless-id-webauthn)Was this useful?