X Enterprises

fastify-xauth-local

Fastify 5 plugin for JWT authentication with role-based access control, supporting multiple independent auth configurations per route prefix.

fastify-xauth-local

JWT authentication with role-based access control (RBAC) for Fastify 5. Supports multiple independent auth configurations per route prefix, HS256 symmetric secrets and RS256 key pairs, optional built-in login/register/password-reset routes, and express-jwt-compatible request.auth patterns.

Installation

npm install @xenterprises/fastify-xauth-local

Quick Start

import Fastify from "fastify";
import xAuthLocal from "@xenterprises/fastify-xauth-local";

const fastify = Fastify();

await fastify.register(xAuthLocal, {
  configs: [
    {
      name: "api",
      prefix: "/api",
      secret: process.env.JWT_SECRET,
      excludedPaths: ["/api/public", "/api/health"],
      local: {
        enabled: true,
        userLookup: async (email) => db.users.findByEmail(email),
        createUser: async (userData) => db.users.create(userData),
      },
    },
  ],
});

// Protected route — request.auth contains the decoded JWT payload
fastify.get("/api/profile", async (request) => ({
  id: request.auth.id,
  email: request.auth.email,
}));

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

Options

Plugin Options

NameTypeDefaultRequiredDescription
activebooleantrueNoEnable/disable the plugin entirely
configsArrayYesArray of auth configuration objects
basePathstringprocess.cwd()NoBase path for resolving relative key file paths

Config Options

Each entry in the configs array:

NameTypeDefaultRequiredDescription
namestringYesUnique identifier for this config
prefixstringYesRoute prefix to protect (e.g. /api)
secretstringYes*Symmetric secret for HS256
publicKeystringYes*Public key content or file path for RS256
privateKeystringYes*Private key content or file path for RS256
algorithmstringautoNoRS256 when keys provided, HS256 for secret
expiresInstring'4d'NoDefault token expiration (e.g. '1h', '7d')
audiencestringNoJWT audience claim for sign/verify
issuerstringNoJWT issuer claim for sign/verify
requestPropertystring'auth'NoProperty name on request for decoded token
credentialsRequiredbooleantrueNoWhether a token is required (false = optional auth)
excludedPathsArray[]NoPaths/patterns to skip auth
getTokenFunctionNoCustom (request) => token extraction function
localObjectNoBuilt-in auth routes — see Local Route Options

* One of secret or publicKey/privateKey is required per config.

Local Route Options

NameTypeDefaultRequiredDescription
enabledbooleanfalseNoEnable built-in login/register/reset routes
loginPathstring{prefix}/localNoRoute prefix for local auth
mePathstring{loginPath}/meNoPath for the /me endpoint
skipUserLookupbooleanfalseNo/me returns token data without a DB call
userLookupFunctionYes**async (email) => user | null
createUserFunctionNoasync (userData) => newUser
passwordResetFunctionNoasync (email, token?, hashedPassword?) => void
saltRoundsnumber10Nobcrypt salt rounds for password hashing

** Required when local.enabled is true and login is used.

Methods

JWT Service (per config instance)

Access via fastify.xauthlocal.get('name').jwt:

  • jwt.sign — Sign a payload and return a JWT string.
  • jwt.verify — Verify a JWT and return the decoded payload.
  • jwt.decode — Decode a JWT without signature verification.

RBAC & Middleware (per config instance)

Access via fastify.xauthlocal.get('name'):

  • requireRole — Create a preHandler that enforces role membership.
  • createMiddleware — Create a custom auth middleware with override options.
  • isExcluded — Check whether a URL/method is excluded from auth for this config.

Password Utilities

Access via fastify.xauthlocal.password:

Built-In Local Routes

Registered at {loginPath} when local.enabled is true:

  • local.loginPOST {loginPath} — Authenticate with email + password.
  • local.registerPOST {loginPath}/register — Create a new user account.
  • local.meGET {loginPath}/me — Return the current authenticated user.
  • local.password-resetPOST/PUT {loginPath}/password-reset — Request or complete a password reset.

Error Reference

All authentication errors use standard HTTP status codes. The error prefix in logs is xAuthLocal.

StatusMessageContext
401No authorization token was foundMissing JWT in request
401jwt expired / invalid signatureInvalid or expired token
401Invalid email or passwordLogin attempt failed
401Authentication requiredUnauthenticated request on protected route
403Access denied. Required role(s): <roles>Insufficient role via requireRole
409An account with this email already existsRegistration conflict
400Invalid or expired reset tokenPassword reset with bad token
500An error occurred during login/registrationUnexpected server error

Environment Variables

The plugin reads no environment variables directly. Pass secrets through config options:

VariableRequiredDescription
JWT_SECRETYes*Symmetric secret for HS256 — pass as configs[].secret
JWT_PUBLIC_KEYYes*Public key for RS256 — pass as configs[].publicKey
JWT_PRIVATE_KEYYes*Private key for RS256 — pass as configs[].privateKey

* Provide either JWT_SECRET or the RSA key pair per config.

How It Works

On registration the plugin iterates the configs array and creates an independent jsonwebtoken-backed JWT service and auth middleware for each entry. A global onRequest hook checks whether the incoming URL starts with each config's prefix; if so, it extracts the bearer token (or calls getToken), verifies it against that config's secret or public key, and attaches the decoded payload to request[requestProperty]. Routes matching excludedPaths skip verification. When local.enabled is true, a sub-plugin is registered at loginPath providing login, register, and password-reset routes — these paths are automatically added to excludedPaths. Each config is stored on fastify.xauthlocal by name, so a token signed by one config cannot be verified by another.

AI Context

package: "@xenterprises/fastify-xauth-local"
type: fastify-plugin
use-when: JWT auth with RBAC for Fastify — HS256/RS256, multiple route prefixes, optional built-in login/register/password-reset routes
decorator: fastify.xauthlocal
request-decorator: request.auth (decoded JWT payload, property name configurable)
env: JWT_SECRET (HS256), JWT_PUBLIC_KEY / JWT_PRIVATE_KEY (RS256) — pass via config options, not read directly
built-in-routes: POST {loginPath}, POST {loginPath}/register, GET {loginPath}/me, POST {loginPath}/password-reset (when local.enabled: true)
Copyright © 2026