@fastify/jwt
JWT authentication plugin for Fastify, decorating routes with sign, verify, and decode utilities built on fast-jwt.
Repository Health
Technical Analysis
@fastify/jwt is the official Fastify plugin for adding JSON Web Token authentication to a Fastify application. Once registered with a secret (or a key-resolution function), it decorates the Fastify instance with jwt.sign, jwt.verify, and jwt.decode, and adds request.jwtVerify()/request.jwtDecode() and reply.jwtSign() so tokens can be issued and checked directly inside route handlers and hooks. Internally it delegates the cryptographic work to fast-jwt, supporting the full range of HMAC, RSA, ECDSA, and EdDSA algorithms, while the plugin layer handles Fastify-specific concerns like error mapping, cookie-based token lookup, and typed configuration.
Beyond basic sign/verify, the plugin covers real-world authentication patterns: extracting tokens from either an Authorization header or a signed cookie (with automatic fallback), rejecting individual tokens via a trusted callback for denylisting, and a namespace option that lets multiple independently-configured JWT instances (for example, short-lived access tokens and long-lived refresh tokens) coexist on the same Fastify instance without naming collisions. It ships hand-written TypeScript definitions with declaration-merging support so payload and request.user shapes can be typed per-project.
What You Get
fastify.jwt.sign/verify/decodeplusrequest.jwtVerify(),request.jwtDecode(), andreply.jwtSign()decorators added to every route- Support for HS*, RS*, ES*, PS*, and EdDSA algorithms via the underlying fast-jwt signer/verifier
- Built-in cookie support for reading tokens from a signed or unsigned cookie, with automatic fallback to the Authorization header
- A
namespaceoption for running multiple independent JWT configurations (e.g. access + refresh tokens) side by side - Hand-written TypeScript definitions with declaration-merging hooks for typing the payload and
request.user
Common Use Cases
- Protecting REST routes with a bearer-token
onRequesthook that populatesrequest.user - Issuing short-lived access tokens and longer-lived refresh tokens stored in httpOnly cookies
- Verifying externally-issued tokens (e.g. from Auth0 or another IdP) in verify-only mode using only a public key
- Rotating signing keys via JWKS by resolving the verification key dynamically per request
- Token denylisting/revocation checks via the
trustedcallback before trusting a decoded payload
Under The Hood
Architecture
The plugin is a single index.js module wrapped with fastify-plugin (fp) so its decorators apply to the encapsulating scope rather than being isolated by Fastify’s plugin boundaries. On registration it validates options, builds a signer/decoder/verifier from fast-jwt, and decorates the Fastify instance with a jwt object (sign, verify, decode, cookie, lookupToken) plus request.jwtVerify/jwtDecode and reply.jwtSign. Async control flow for the multi-step verify path (resolve secret → verify token → check trusted callback) is sequenced with the steed waterfall helper rather than native async/await, preserving both callback- and Promise-based call signatures for consumers. The namespace option turns fastify.jwt into a map of per-namespace JWT instances instead of a single instance, guarding against duplicate namespace registration. A validatorCache Map caches verifiers by string key to avoid rebuilding them on every call when using default options with a static key.
Tech Stack
A CommonJS Node.js module targeting Fastify 5.x (declared via the fastify-plugin metadata). Core dependencies are fast-jwt for all JWT cryptographic operations, fastify-plugin for correct plugin registration semantics, @fastify/error for creating typed, coded error classes, @lukeed/ms for parsing human-readable time spans ("10h", "7d") into milliseconds, and steed for callback-based async sequencing. Development tooling includes eslint with the neostandard config, c8 for coverage enforcement, and tstyche for TypeScript type-testing, with fastify, @fastify/cookie, and @fastify/rate-limit used only in integration tests.
Code Quality
The test suite spans four files (jwt.test.js, jwt-async.test.js, namespace.test.js, options.test.js) totaling well over 3,000 lines, run against Node’s native node:test runner with a coverage gate of c8 --100 — full-coverage is enforced, not aspirational. TypeScript correctness is checked separately via tstyche against hand-written .d.ts definitions and dedicated .tst.ts type-assertion files. Errors are never generic: every failure path (missing token, bad request format, expired token, untrusted token, invalid signature) is mapped to a distinct FST_JWT_* error code created with @fastify/error, and errors bubbling up from fast-jwt are explicitly translated (wrapError) into these domain-specific types rather than leaked as-is. Naming is consistent and intention-revealing (checkAndMergeSignOptions, lookupToken), and CI runs via GitHub Actions on every change.
What Makes It Unique
Relative to rolling JWT handling by hand with jsonwebtoken inside a Fastify app, this plugin’s main technical differentiators are its namespace mechanism for cleanly running multiple independent JWT configurations in one instance, its layered token-lookup fallback (header, then cookie, then a fully custom extractToken function), and its function-based secret resolution that transparently supports static secrets, JWKS-backed dynamic key lookup, and Promise- or callback-style resolution behind one consistent option. These are extensions of well-established JWT patterns rather than a novel approach to authentication itself.