npm install passkifyNo peer dependencies, no post-install step, and nothing to configure at install time.
Entry points
The package has two halves that never run in the same place.
passkifyserver (Node)The server API. Re-exports everything from passkify/server, because that
is what a bare import ... from 'passkify' resolves to in a Node process.
passkify/serverserver (Node)The same module, named explicitly. Prefer this in a codebase where server and client code sit side by side, since the import path then says which side of the wire a file belongs to.
passkify/clientbrowserThe browser half. No Node built-ins, so it bundles anywhere.
import { PasskeyServer, MemoryStore } from 'passkify'; // Node
import { register, login } from 'passkify/client'; // browserNothing stops you importing the server half into browser code
It used to stop itself: passkify/server pulled in node:crypto and a browser
build failed on it. Verification is pure WebCrypto now, which is what makes the
edge runtimes work — and the same change means the server half bundles for a
browser without a word. You get the entire verifier, roughly six times the size
of passkify/client, and no error. Import the subpath.
Requirements
Node>= 20Or Bun, Deno, Cloudflare Workers or Vercel Edge — verification is pure WebCrypto, so the server half is not tied to Node at all.
Module formatESM and CommonJSBoth builds ship. import resolves to the ESM build, require to the
CommonJS one, and types come from a single set of declarations.
TypeScriptoptional, >= 5.0Types are bundled. moduleResolution must be bundler, node16 or
nodenext for the subpath exports to resolve.
HTTPSrequired in productionWebAuthn only runs in a secure context. localhost counts as one, which is
why local development works over plain HTTP. Everything else needs real
TLS.
Browser support
Passkeys work in every current browser. The features that are newer than the base API degrade rather than break.
| Feature | Chrome | Safari | Firefox | Edge |
|---|---|---|---|---|
| WebAuthn | 67 | 14 | 60 | 18 |
| Passkeys (discoverable, synced) | 108 | 16 | 122 | 108 |
| Conditional UI (autofill) | 108 | 16 | 119 | 108 |
passkify/client feature-detects all of it. isSupported(),
isPlatformAuthenticatorAvailable() and isAutofillAvailable() let you decide
what to show; see the client API.
Runtime footprint
Runtime dependencies0The only supply chain is Node's own crypto. CBOR decoding, COSE key
parsing, attestation verification and base64url are all in the package.
Browser bundle~6.5 kB gzippedThat is passkify/client plus the shared encoding and error modules. The
server half never reaches the browser.
Why zero dependencies is a feature here, not a slogan
This code sits directly on attacker-controlled bytes. Every dependency in that path is code you are trusting without reading and a version you have to keep patched. The parser is about four hundred lines; that is small enough that reading it is a realistic thing to ask of a security reviewer, and small enough that shipping it beats importing it.
Verify the install
import { PasskeyServer, MemoryStore } from 'passkify';
const passkeys = new PasskeyServer({
rpName: 'Test',
origin: 'http://localhost:3000',
store: new MemoryStore(),
});
const { options } = await passkeys.startRegistration({ username: 'ada' });
console.log(options.rp.id); // 'localhost'
console.log(options.challenge); // 43 base64url charactersIf the constructor throws, read the message. Configuration errors are checked eagerly and name the fix; see configuration.