Skip to content

About

Open-source OWASP-aligned token management library for Node.js.

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

OwlTokenGuard

OwlTokenGuard logo


npm package Node.js License

Open-source OWASP-aligned token management library for Node.js.

OwlTokenGuard, published as @restingowlorg/owltokenguard, gives your backend a focused token-management surface: access and refresh JWT issuance, signature-first verification, refresh-token rotation (RTR), session revocation, and freshness controls after account-security events. It ships Express and Fastify middleware and stays out of your persistence layer — you wire hooks for refresh storage and session termination.

What You Get

  • Access + refresh issuance: Generate access JWTs and optional refresh JWTs from one TokenManager.
  • Refresh token rotation (RTR): One-time refresh consumption with an OAuth-style token response.
  • Fail-shut verification: Signature-first checks with issuer, audience, temporal, purpose, and algorithm allowlists.
  • Session revocation hooks: Revoke by jti, raw token, cutoff timestamp, or custom policy — persistence is yours.
  • Reauthentication freshness: Stamp and enforce reauth_at so sessions go stale after email or MFA changes.
  • Opaque reference tokens: Issue high-entropy backend session references when you need non-JWT handles.
  • Optional payload encryption: Encrypt JWT payloads before signing with a pluggable cipher (AES-256-GCM).
  • Framework middleware: Express and Fastify verification middleware with a typed auth context.
  • Typed, predictable results: Consistent shapes across issuance, rotation, verification, and termination.

Support Matrix

Area Current Support
Runtime Node.js 18, 20, and 22
Language TypeScript, JavaScript
Module output CommonJS
Signing algorithms HS256, HS512, RS256, ES256
HTTP frameworks Express, Fastify
Core flows Issue, Verify, Rotate, Revoke, Terminate

Installation

npm install @restingowlorg/owltokenguard

Quick Start

import { createTokenManager } from "@restingowlorg/owltokenguard";

const tokenManager = createTokenManager({
  algorithm: "HS256",
  hmacSecret: process.env.JWT_SECRET!,
  issuer: "https://issuer.example",
  trustedIssuers: ["https://issuer.example"],
  audience: "my-api",
  expiresInSeconds: 900,
  refreshTokenEnabled: true,
  refreshTokenExpiresInSeconds: 60 * 60 * 24 * 7,
  onSessionTerminate: async ({ jti }) => {
    // Remove session jti from your store (DB, cache, etc.)
    await sessionStore.revoke(jti);
  },
});

const issued = await tokenManager.generateAccessToken(
  { sub: "user-123", role: "admin" },
  { reauthAt: Math.floor(Date.now() / 1000) },
);

const verified = await tokenManager.verify(issued.token, { purpose: "access" });
console.log(verified.payload.sub); // "user-123"

Package Boundary

OwlTokenGuard is the token management library. It owns token-level cryptography: JWT signing, verification, registered claims such as iss and aud, token purpose checks, optional payload encryption, and opaque reference-token generation. OwlSessionGuard is the session management library and owns session records, storage adapters, idle and absolute timeouts, revoke-all behavior, device binding, breach response, and persistent refresh-token rotation state.

The refresh APIs in this package issue and validate refresh JWTs and expose hooks such as onRefreshTokenIssued, consumeRefreshToken, and onSessionTerminate as integration points. Back those hooks with the session-management library or an application-owned store; this package does not own durable session storage.

Signing & Key Material

OwlTokenGuard defaults to RS256 at the library level; HS256/HS512 require a high-entropy HMAC secret (minimum 64 characters). ES256 requires the P-256 / prime256v1 curve; RS256 verification rejects keys below 2048 bits.

Symmetric (HS256 / HS512)

import { createTokenManager } from "@restingowlorg/owltokenguard";

const tokenManager = createTokenManager({
  algorithm: "HS256",
  hmacSecret: process.env.JWT_SECRET!,
  expiresInSeconds: 900,
  allowedAlgorithms: ["HS256"],
  onSessionTerminate: async () => {},
});

Asymmetric (RS256 / ES256)

import { createTokenManager } from "@restingowlorg/owltokenguard";

const tokenManager = createTokenManager({
  algorithm: "RS256",
  signingKey: {
    type: "asymmetric",
    privateKey: process.env.JWT_PRIVATE_KEY!,
    publicKey: process.env.JWT_PUBLIC_KEY!,
  },
  expiresInSeconds: 900,
  allowedAlgorithms: ["RS256"],
  onSessionTerminate: async () => {},
});

Optional Payload Encryption

import {
  createTokenManager,
  Aes256GcmCipher,
} from "@restingowlorg/owltokenguard";

const encryptionKey = Buffer.from(
  process.env.PAYLOAD_ENCRYPTION_KEY!,
  "base64",
);

const tokenManager = createTokenManager({
  algorithm: "HS256",
  hmacSecret: process.env.JWT_SECRET!,
  expiresInSeconds: 900,
  payloadCipher: new Aes256GcmCipher(encryptionKey),
  onSessionTerminate: async () => {},
});

Framework Integration

Express

import express from "express";
import {
  createTokenManager,
  expressVerifyToken,
} from "@restingowlorg/owltokenguard";

const app = express();
app.use(express.json());

const manager = createTokenManager({
  algorithm: "HS256",
  hmacSecret: process.env.JWT_SECRET!,
  expiresInSeconds: 900,
  onSessionTerminate: async () => {},
});

const requireAccessToken = expressVerifyToken(manager, { purpose: "access" });

app.get("/api/me", requireAccessToken, (req, res) => {
  res.json({
    sub: req.auth?.payload.sub,
    jti: req.auth?.jti,
  });
});

Fastify

import Fastify from "fastify";
import {
  createTokenManager,
  fastifyVerifyToken,
} from "@restingowlorg/owltokenguard";

const app = Fastify();

const manager = createTokenManager({
  algorithm: "HS256",
  hmacSecret: process.env.JWT_SECRET!,
  expiresInSeconds: 900,
  onSessionTerminate: async () => {},
});

const requireAccessToken = fastifyVerifyToken(manager, { purpose: "access" });

app.get("/api/me", { preHandler: requireAccessToken }, async (request) => {
  return {
    sub: request.auth?.payload.sub,
    jti: request.auth?.jti,
  };
});

Core Usage

Issue Access + Refresh Tokens

import type {
  AccessTokenResult,
  TokenResult,
} from "@restingowlorg/owltokenguard";

const issued: AccessTokenResult = await tokenManager.generateAccessToken(
  { sub: "user-123", role: "admin" },
  { reauthAt: Math.floor(Date.now() / 1000) },
);

// issued.token, issued.claims, issued.refreshToken, issued.refreshClaims

const combined: TokenResult = await tokenManager.generate({ sub: "user-123" });
// combined.token + combined.referenceToken

Verify Tokens

import type { VerifyResult } from "@restingowlorg/owltokenguard";

const auth: VerifyResult = await tokenManager.verify(token, {
  purpose: "access",
  audience: "my-api",
  trustedIssuers: ["https://issuer.example"],
});

Rotate Refresh Tokens (OAuth-style)

import type { RotateResult } from "@restingowlorg/owltokenguard";

const rotated: RotateResult = await tokenManager.rotate(refreshToken);

// RFC 6749-compatible payload for POST /auth/refresh:
// rotated.oauth = { access_token, token_type, expires_in, refresh_token }

Wire rotation persistence with onRefreshTokenIssued and consumeRefreshToken so each refresh JWT is stored once and consumed atomically on rotate. Persistent refresh-token state, replay response, logout-all-devices, idle timeout, and absolute session lifetime should be implemented by OwlSessionGuard or your application session store.

Store Token Digests, Not Raw Tokens

import { createTokenDigest } from "@restingowlorg/owltokenguard";

const digest = createTokenDigest(refreshToken, {
  pepper: process.env.TOKEN_DIGEST_PEPPER!,
});

await refreshTokenStore.save({ digest, subject: "user-123" });

Use token digests for lookup values when integrating with OwlSessionGuard or an application-owned session store. Prefer the peppered HMAC form for stored refresh-token records because it remains safer if the database leaks.

Revoke and Terminate Sessions

// Logout-style: verify the token, then terminate its session
await tokenManager.revokeToken(refreshToken, { purpose: "refresh" });

// Server-side: terminate by jti or verified claims
await tokenManager.terminate({ jti: "session-jti" });

// Invalidate all tokens issued before a cutoff (logout-all-devices)
await tokenManager.terminate(claims, {
  sub: "user-123",
  invalidateBefore: Math.floor(Date.now() / 1000),
});

Note: onSessionTerminate is required at config time. The library invokes it on explicit termination and during access-token rotation when previousSession is supplied. You implement storage removal — the library does not persist sessions for you.

Session Revocation & Freshness Patterns

A) Revoke by jti

Persist revoked jti values and reject them in isSessionRevoked.

B) Invalidate tokens issued before a cutoff

Use getTokensInvalidBefore(sub) to reject any token where iat is strictly before the stored cutoff.

C) Enforce reauthentication freshness (reauth_at)

When sensitive account data changes (email, MFA, recovery methods), bump a minimum freshness timestamp and reject tokens with stale reauth_at.

const minimumReauthAtBySub = new Map<string, number>();

const manager = createTokenManager({
  algorithm: "HS256",
  hmacSecret: process.env.JWT_SECRET!,
  expiresInSeconds: 900,
  getMinimumReauthAt: async (sub) => minimumReauthAtBySub.get(sub),
  onSessionTerminate: async () => {},
});

// On email change / MFA change:
minimumReauthAtBySub.set("user-123", Math.floor(Date.now() / 1000));

OAuth Refresh Endpoint Example

app.post("/auth/refresh", async (req, res) => {
  const refreshToken = req.body?.refresh_token;
  if (!refreshToken) {
    return res.status(400).json({ error: "invalid_request" });
  }

  try {
    const result = await manager.rotate(refreshToken);
    return res.status(200).json(result.oauth);
  } catch {
    return res.status(401).json({ error: "invalid_grant" });
  }
});

Configuration Options

Core TokenConfig

Option Type Purpose
algorithm SigningAlgorithm Preferred signing algorithm (HS*, RS256, ES256)
issuer string Issuer claim (iss) stamped onto issued JWTs
hmacSecret string Shared secret for symmetric signing (min 64 chars for HS*)
signingKey key material Private/public key material for asymmetric signing
expiresInSeconds number Access token exp offset (positive integer, validated at startup)
refreshTokenEnabled boolean Issue a refresh JWT alongside each access token
refreshTokenExpiresInSeconds number Refresh token exp offset (required when refresh is enabled)
onSessionTerminate hook (required) Persist session removal or cutoff invalidation
onRefreshTokenIssued hook Persist refresh session metadata after issuance
consumeRefreshToken hook Atomically mark a refresh token consumed during rotate()
isSessionRevoked hook Per-token revocation check during verification
getTokensInvalidBefore hook Reject tokens with iat before a subject-level cutoff
requireReauthAtClaim boolean Require reauth_at freshness marker on verify
getMinimumReauthAt hook Reject tokens with stale reauth_at
payloadCipher PayloadCipher Optional payload encryption before signing
maxTokenBytes number Max JWT string size accepted on verify (default: 8192)
allowedAlgorithms SigningAlgorithm[] Verification allowlist (defaults to configured algorithm)
trustedIssuers string[] Issuer allowlist for verification
audience string | string[] Audience claim stamped during issuance and expected on verify

Verification Options (verify)

Option Type Purpose
purpose "access" | "id" | "refresh" Enforce token intent
audience string | string[] Override audience check
trustedIssuers string[] Override issuer allowlist
clockToleranceSeconds number Temporal tolerance
requireTemporalClaims boolean Require exp and nbf
requireReauthAtClaim boolean Require reauth_at for this call
minimumReauthAt number Per-request reauth freshness cutoff

Response & Error Model

Verification and issuance return typed results (AccessTokenResult, VerifyResult, RotateResult, etc.).

Exported errors for application handling:

  • TokenGenerationError — issuance or termination input failures
  • TokenVerificationError — signature, claims, revocation, or freshness failures
  • SecurityConfigurationError — invalid keys, algorithms, or config at startup
  • UntrustedKeySourceError — untrusted jku / x5u header sources

Typical verify handler:

import { TokenVerificationError } from "@restingowlorg/owltokenguard";

try {
  await manager.verify(token, { purpose: "access" });
} catch (error) {
  if (error instanceof TokenVerificationError) {
    // return 401
  }
  throw error;
}

OWASP Alignment

Here's what OwlTokenGuard enforces in code, traced to OWASP JWT Cheat Sheet, Session Management Cheat Sheet, and ASVS token/session controls.

Token Verification

Control What the library does OWASP reference
Signature-first validation Header and signature are verified before payload claims are trusted. JWT Cheat Sheet — validate signatures
Algorithm allowlist Only configured algorithms pass verification; "none" is blocked. JWT Cheat Sheet — do not accept none
Key confusion prevention HMAC algorithms require symmetric material; asymmetric algorithms require public keys. ASVS — prevent algorithm/key confusion
Temporal claim checks exp, nbf, and optional clock tolerance are enforced. JWT Cheat Sheet — validate temporal claims
Issuer and audience Optional trusted issuers and audience checks on every verify. JWT Cheat Sheet — validate iss and aud
Purpose separation Access, ID, and refresh token types can be enforced to prevent cross-use. OAuth / OIDC token_use separation
Oversized token rejection JWT strings above 8 KB (configurable) are rejected before parsing. DoS hardening — limit untrusted input size
Weak key rejection RS256 requires ≥2048-bit RSA keys; ES256 requires P-256 (prime256v1). Cryptographic key strength best practices

Refresh Token Rotation

Control What the library does OWASP reference
One-time refresh use consumeRefreshToken must atomically mark a refresh JWT consumed before rotation continues. Session Management — detect refresh token reuse
Rotation on refresh A successful rotate issues new access + refresh tokens and terminates the prior refresh session. OAuth 2.0 refresh token rotation (RTR)
OAuth-compatible output rotate() exposes an RFC 6749-style { access_token, token_type, expires_in, refresh_token } payload. OAuth 2.0 token response

Session Revocation & Freshness

Control What the library does OWASP reference
Server-side revocation hooks isSessionRevoked, terminate, and revokeToken let you invalidate sessions without waiting for exp. Session Management — server-side session termination
Cutoff invalidation getTokensInvalidBefore rejects tokens issued before a stored cutoff (logout-all-devices). Session Management — invalidate active sessions
Reauthentication freshness reauth_at is stamped at issuance and enforced via getMinimumReauthAt after account-security changes. Session Management — re-auth after credential change

Security Notes

The table above covers what this library actually does. It's not an OWASP certification, and it won't make your app ASVS-compliant on its own. You still need to handle:

  • TLS and secure transport
  • secure key storage and rotation
  • refresh-token storage hardening in your database or cache
  • brute-force protection, rate limits, and lockout on auth endpoints
  • CSRF defenses where cookies carry tokens
  • account recovery and MFA flows
  • authorization and role enforcement beyond token verification

For technical boundaries and threat model details, see docs/SECURITY_MODEL.md. To report a vulnerability, follow SECURITY.md.

Roadmap

OwlTokenGuard is part of the wider Resting Owl effort to ship secure-by-default tooling for Node.js backends.

What's coming next:

  • Deeper framework integrations: First-party helpers for NestJS, Next.js API routes, and serverless runtimes
  • JWKS & key rotation: Built-in JWKS fetch/rotation patterns for multi-tenant issuers
  • Stronger session stores: Reference adapters and patterns for Redis, PostgreSQL, and MongoDB session persistence
  • Tighter owlauth pairing: End-to-end examples combining @restingowlorg/owlauth login flows with OwlTokenGuard tokens
  • Broader Resting Owl package family: Adjacent packages for rate limiting, audit logging, CSRF protection, and secrets management

Community

Website LinkedIn GitHub Issues Developer Guide

License

MIT License

About

Open-source OWASP-aligned token management library for Node.js.

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages