X Enterprises

fastify-xauth-jwks

Path-based JWT/JWKS validation for Fastify v5. Protect multiple route prefixes with independent JWKS providers.

fastify-xauth-jwks

Path-based JWT validation using JWKS (JSON Web Key Sets). Register once and protect any number of URL prefixes — each with its own JWKS endpoint, caching policy, and excluded sub-paths.

Breaking change (1.0): the decorator was renamed from fastify.xAuth to fastify.xAuthJwks. Update any code that accessed fastify.xAuth.validators.<name> to use fastify.xAuthJwks.validators.<name>.

Installation

npm install @xenterprises/fastify-xauth-jwks

Quick Start

import Fastify from "fastify";
import xAuth from "@xenterprises/fastify-xauth-jwks";

const fastify = Fastify();

await fastify.register(xAuth, {
  paths: {
    admin: {
      pathPattern: "/admin",
      jwksUrl: process.env.ADMIN_JWKS_URL,
      excludedPaths: ["/health"],
    },
    portal: {
      pathPattern: "/portal",
      jwksUrl: process.env.PORTAL_JWKS_URL,
    },
  },
});

// Protected — requires valid JWT
fastify.get("/admin/dashboard", (request) => {
  return { userId: request.auth.userId, user: request.user };
});

// Excluded — no auth required
fastify.get("/admin/health", () => ({ status: "ok" }));

await fastify.listen({ port: 3000 });

Options

The plugin accepts a single paths object. Each key is a logical name for the path and each value is its configuration.

OptionTypeDefaultRequiredDescription
pathsobjectYesMap of path name → path configuration. Must contain at least one entry.

Path Configuration

OptionTypeDefaultRequiredDescription
pathPatternstring/<pathName>NoURL prefix to protect. All routes starting with this prefix require a valid JWT.
jwksUrlstringOne of jwksUrl/jwksDataRemote JWKS endpoint URL for token verification.
jwksDataobjectOne of jwksUrl/jwksDataLocal JWKS data ({ keys: [...] }) or a single JWK object. Useful for development and testing.
activebooleantrueNoSet to false to skip registering this path entirely.
excludedPathsstring[][]NoSub-paths that bypass authentication (e.g., ["/health", "/docs"]).
jwksCooldownDurationnumber30000NoMinimum milliseconds between JWKS refetches (remote JWKS only).
jwksCacheMaxAgenumber1800000NoMaximum milliseconds to cache remote JWKS keys.
enablePayloadCachebooleantrueNoCache verified JWT payloads in memory to skip re-verification of repeated tokens.
payloadCacheTTLnumber300000NoTTL in milliseconds for cached JWT payloads.

Request Properties

On authenticated requests the plugin sets:

PropertyTypeDescription
request.userobjectFull JWT payload (all claims).
request.auth.pathstringName of the path that authenticated this request.
request.auth.userIdstringThe sub claim from the JWT.
request.auth.payloadobjectFull JWT payload (same as request.user).

Methods

Validator Object (fastify.xAuthJwks.validators.<name>)

Each registered path creates a validator accessible at fastify.xAuthJwks.validators.<name>:

  • verifyJWT — Verify a JWT string against this path's JWKS. Returns the payload or null.
  • clearPayloadCache — Clear the in-memory JWT payload cache for this path.
  • getPayloadCacheStats — Get cache statistics (size, enabled, ttl) for monitoring.

Utility Exports (@xenterprises/fastify-xauth-jwks/utils)

  • extractToken — Extract Bearer token from the Authorization header.
  • hasRole — Check if a JWT payload contains any of the specified roles.
  • hasPermission — Check if a JWT payload contains any of the specified permissions.
  • getUserId — Get the user ID from request.auth.userId or request.user.sub.
  • getAuthEndpoint — Get the auth path name from request.auth.path.
  • requireRole — Fastify preHandler factory that replies 403 if the user lacks the role.
  • requirePermission — Fastify preHandler factory that replies 403 if the user lacks the permission.
  • requireEndpoint — Fastify preHandler factory that replies 403 if the request was not authenticated by the named path.
  • decodeToken — Decode JWT payload without verification.
  • decodeHeader — Decode JWT header without verification.

Error Reference

Runtime Errors (HTTP responses)

HTTP StatusErrorWhen
401Access token requiredNo Authorization: Bearer <token> header on a protected route.
401Invalid tokenToken failed JWKS verification, is expired, or missing the sub claim.
401Authentication failedUnexpected error during authentication (logged server-side).
403Insufficient permissionsrequireRole or requirePermission check failed.
403Must authenticate via <name> endpointrequireEndpoint check — request was authenticated by a different path.

Startup Errors (thrown during registration)

Every message names the plugin (xauth), the offending option, and shows a usage example.

Error message (shape)Cause
xauth: missing required option `paths` (object), e.g. `app.register(xAuth, { paths: ... })` No options (or no paths) passed to the plugin.
xauth: option `paths` must be an object mapping path names to configurations, e.g. ...paths is not a plain object (or is an array).
xauth: option `paths` must contain at least one path configuration, e.g. ...paths is an empty object.
xauth: path '<name>' configuration must be an object, e.g. ...A path entry is not an object.
xauth: path '<name>' requires either `jwksUrl` or `jwksData`, e.g. ...Path config has no JWKS source.
xauth: path '<name>' cannot specify both `jwksUrl` and `jwksData` — pick one JWKS source, e.g. ...Path config specifies both JWKS sources.
xauth: path '<name>' option `jwksUrl` must be a valid URL string, e.g. ...jwksUrl is not a parseable URL.
xauth: path '<name>' option `jwksData` must be a JWKS object (`{ keys: [...] }`) or a single JWK, e.g. ...jwksData has neither a keys array nor a kty field.
xauth: path '<name>' option `pathPattern` must be a string starting with '/', e.g. ...Invalid pathPattern.
xauth: path '<name>' option `excludedPaths` must be an array of strings, e.g. ...Invalid excludedPaths.
xauth: path '<name>' option `jwksCooldownDuration`/`jwksCacheMaxAge`/`payloadCacheTTL` must be a positive number (milliseconds), e.g. ...Non-positive or non-numeric cache timing (message names the specific option).
xauth: path '<name>' option `enablePayloadCache` must be a boolean, e.g. ...Invalid enablePayloadCache.

Environment Variables

The plugin does not read environment variables directly. Pass JWKS URLs from your environment:

VariableRequiredDescription
ADMIN_JWKS_URLPer pathJWKS endpoint URL for admin path validation.
PORTAL_JWKS_URLPer pathJWKS endpoint URL for portal path validation.

How It Works

At registration the plugin iterates paths and creates a PathValidator for each active entry. Remote JWKS endpoints are wrapped with jose.createRemoteJWKSet (configurable cooldown and max-age); local JWKS data is wrapped with jose.createLocalJWKSet. An onRequest hook checks request.url.startsWith(pathPattern), skips any excludedPaths, extracts the Bearer token, verifies it against the path's JWKS, and populates request.user and request.auth. Two caching layers reduce latency: the jose library caches remote JWKS keys, and an optional in-memory Map caches verified JWT payloads keyed by raw token string with a configurable TTL.

AI Context

package: "@xenterprises/fastify-xauth-jwks"
type: fastify-plugin
use-when: Path-based JWT validation against remote or local JWKS — multiple route prefixes each with their own JWKS provider
decorator: fastify.xAuthJwks (validators.<name>.verifyJWT, clearPayloadCache, getPayloadCacheStats)
request-decorators: request.user (full JWT payload), request.auth.userId, request.auth.path, request.auth.payload
utility-exports: extractToken, hasRole, hasPermission, getUserId, getAuthEndpoint, requireRole, requirePermission, requireEndpoint, decodeToken, decodeHeader
import-utils: "@xenterprises/fastify-xauth-jwks/utils"
Copyright © 2026