npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xenterprises/fastify-xauth-jwks

v1.2.1

Published

Fastify plugin for path-based JWT/JWKS validation. Protect multiple paths with independent JWKS providers.

Readme

@xenterprises/fastify-xauth-jwks

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

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: "https://your-auth.com/.well-known/jwks.json",
      excludedPaths: ["/health"],
    },
    portal: {
      pathPattern: "/portal",
      jwksUrl: "https://your-auth.com/.well-known/jwks.json",
    },
  },
});

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

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

// Public route - not under any protected path
fastify.get("/public/info", () => ({ info: "open" }));

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

Options

The plugin accepts a single paths object where each key is a path name and each value is a path configuration:

| Option | Type | Default | Required | Description | |---|---|---|---|---| | paths | object | — | Yes | Map of path name to path configuration. Must contain at least one entry. |

Path Configuration

| Option | Type | Default | Required | Description | |---|---|---|---|---| | pathPattern | string | /<pathName> | No | URL prefix to protect. All routes starting with this prefix require authentication. | | jwksUrl | string | — | One of jwksUrl / jwksData | Remote JWKS endpoint URL for token verification. | | jwksData | object | — | One of jwksUrl / jwksData | Local JWKS data ({ keys: [...] }) or a single JWK object for development/testing. | | active | boolean | true | No | Set to false to skip registration of this path. | | excludedPaths | string[] | [] | No | Sub-paths that bypass authentication (e.g., ["/health", "/docs"]). | | jwksCooldownDuration | number | 30000 | No | Minimum milliseconds between JWKS refetches (remote only). | | jwksCacheMaxAge | number | 1800000 | No | Maximum age in milliseconds for cached JWKS keys (remote only). | | enablePayloadCache | boolean | true | No | Cache verified JWT payloads in memory to avoid re-verification. | | payloadCacheTTL | number | 300000 | No | Time-to-live in milliseconds for cached JWT payloads. |

Decorated Properties

After registration, the plugin decorates fastify.xAuth:

| Property | Type | Description | |---|---|---| | fastify.xAuth.validators | object | Map of path name to validator object. |

Validator Object

Each validator in fastify.xAuth.validators.<name> exposes:

| Property / Method | Type | Description | |---|---|---| | name | string | Path name (e.g., "admin"). | | pathPattern | string | URL prefix being protected. | | jwksUrl | string \| undefined | Remote JWKS URL if configured. | | config | object | Caching configuration values. | | verifyJWT(token) | async function | Verify a JWT string. Returns the payload object or null. | | clearPayloadCache() | function | Clear the in-memory JWT payload cache. | | getPayloadCacheStats() | function | Returns { size, enabled, ttl } for monitoring. |

Request Properties

On authenticated requests, the plugin sets:

| Property | Type | Description | |---|---|---| | request.user | object | Full JWT payload (claims). | | request.auth.path | string | Name of the path that authenticated this request. | | request.auth.userId | string | The sub claim from the JWT. | | request.auth.payload | object | Full JWT payload (same as request.user). |

Utility Exports

Import from @xenterprises/fastify-xauth-jwks/utils:

import {
  extractToken,
  hasRole,
  hasPermission,
  getUserId,
  getAuthEndpoint,
  requireRole,
  requirePermission,
  requireEndpoint,
  decodeToken,
  decodeHeader,
} from "@xenterprises/fastify-xauth-jwks/utils";

| Utility | Signature | Description | |---|---|---| | extractToken(request) | (req) => string \| null | Extract Bearer token from Authorization header. | | hasRole(user, roles) | (user, string \| string[]) => boolean | Check if user has any of the specified roles (reads user.roles). | | hasPermission(user, perms) | (user, string \| string[]) => boolean | Check if user has any of the specified permissions (reads user.permissions). | | getUserId(request) | (req) => string \| null | Get user ID from request.auth.userId or request.user.sub. | | getAuthEndpoint(request) | (req) => string \| null | Get the auth path name from request.auth.path. | | requireRole(roles) | (string \| string[]) => preHandler | Fastify preHandler that returns 403 if user lacks the role. | | requirePermission(perms) | (string \| string[]) => preHandler | Fastify preHandler that returns 403 if user lacks the permission. | | requireEndpoint(name) | (string) => preHandler | Fastify preHandler that returns 403 if request was not authenticated by the named path. | | decodeToken(token) | (string) => object | Decode JWT payload without verification (re-export of jose.decodeJwt). | | decodeHeader(token) | (string) => object | Decode JWT header without verification (re-export of jose.decodeProtectedHeader). |

Usage with Route Hooks

import { requireRole, requirePermission } from "@xenterprises/fastify-xauth-jwks/utils";

fastify.get("/admin/settings", {
  preHandler: requireRole("admin"),
  handler: async (request) => ({ settings: "..." }),
});

fastify.delete("/admin/users/:id", {
  preHandler: requirePermission("users:delete"),
  handler: async (request) => ({ deleted: true }),
});

Environment Variables

The plugin itself does not read environment variables. Your application should pass JWKS URLs from environment variables:

| Variable | Required | Description | |---|---|---| | ADMIN_JWKS_URL | Per path | JWKS endpoint for admin path validation. | | PORTAL_JWKS_URL | Per path | JWKS endpoint for portal path validation. |

Example:

await fastify.register(xAuth, {
  paths: {
    admin: {
      pathPattern: "/admin",
      jwksUrl: process.env.ADMIN_JWKS_URL,
    },
  },
});

Error Reference

| HTTP Status | Error | When | |---|---|---| | 401 | Access token required | No Authorization: Bearer <token> header present on a protected route. | | 401 | Invalid token | Token failed JWKS verification, is expired, or missing sub claim. | | 401 | Authentication failed | Unexpected error during authentication (logged server-side). | | 403 | Insufficient permissions | requireRole or requirePermission check failed. | | 403 | Must authenticate via <name> endpoint | requireEndpoint check failed. |

Startup errors (thrown during plugin registration):

| Error Message | Cause | |---|---| | xAuth: options object is required | No options passed to the plugin. | | xAuth: 'paths' option is required and must contain at least one path configuration | Empty or missing paths option. | | <name>: Either jwksUrl or jwksData is required | Path config missing both JWKS source options. | | <name>: Cannot specify both jwksUrl and jwksData | Path config has both JWKS source options. |

How It Works

  1. Registration: For each entry in paths, the plugin creates a path validator. Remote JWKS endpoints are set up via jose.createRemoteJWKSet with configurable caching; local JWKS data uses jose.createLocalJWKSet.

  2. Request Hook: An onRequest hook checks every incoming request URL against each registered pathPattern using String.startsWith(). If the URL matches a protected path and is not in excludedPaths, the hook extracts the Bearer token and verifies it against the path's JWKS.

  3. Caching: Two levels of caching are used. JWKS keys are cached by the jose library with configurable cooldown and max age. JWT payloads can optionally be cached in a Map keyed by the raw token string, with configurable TTL.

  4. Request Decoration: On successful verification, request.user is set to the full JWT payload and request.auth is set with the path name, user ID, and payload for downstream route handlers.

Tests

npm test
# 63 tests passing

License

UNLICENSED