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

@access-tokens/express

v1.1.0

Published

Express routes and middleware for personal access token authentication

Downloads

997

Readme

@access-tokens/express

npm License: ISC

Express routes and middleware for Personal Access Token (PAT) authentication with OAuth 2.0-compatible JWT token exchange.

Features

  • Ready-to-Use Routes: Pre-built authentication and admin token management endpoints
  • JWT Token Exchange: OAuth 2.0-compatible token endpoint for PAT-to-JWT exchange
  • Express Middleware: requireJwt, requireAdmin, and requireRole middleware for route protection
  • JOSE Integration: Industry-standard JWT signing and verification
  • TypeScript: Full type safety with Express request augmentation
  • Flexible Configuration: Customizable paths, token lifetime, and key management

Installation

npm install @access-tokens/express @access-tokens/core

Quick Start

import express from "express";
import { DynamoDBPat } from "@access-tokens/core";
import {
  createAuthRouter,
  createAdminTokensRouter,
  createRequireJwt,
  createRequireAdmin,
  createRequireRole,
  buildSignerVerifier,
  generateKeySet,
} from "@access-tokens/express";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";

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

// Initialize DynamoDB
const dynamoClient = new DynamoDBClient({ region: "us-east-1" });
const docClient = DynamoDBDocumentClient.from(dynamoClient);
const pat = new DynamoDBPat({ tableName: "tokens", docClient });

// Generate JWT signing keys
const keySet = await generateKeySet("my-key-id-1");
const signerVerifier = await buildSignerVerifier({
  keySet,
  issuer: "my-app",
  ttl: "1h",
});

// Add authentication and token admin routes
app.use("/auth", createAuthRouter({ pat, signerVerifier }));
app.use("/admin", createAdminTokensRouter({ pat, signerVerifier }));

const requireJwt = createRequireJwt({ signerVerifier });
const requireAdmin = createRequireAdmin();
const requireEditor = createRequireRole({ role: "editor" });

// Your protected routes
app.get("/api/data", requireJwt, (req, res) => {
  res.json({
    message: "User data",
    user: req.user, // { sub, owner, admin, roles }
  });
});

app.get("/api/admin/data", requireJwt, requireAdmin, (req, res) => {
  res.json({
    message: "Admin data",
    user: req.user, // { sub, owner, admin, roles }
  });
});

app.put("/api/content", requireJwt, requireEditor, (req, res) => {
  res.json({
    message: "Content updated",
    user: req.user, // { sub, owner, admin, roles }
  });
});

app.listen(3000, () => console.log("Server running on port 3000"));

API Reference

Routes

createAuthRouter(options)

Creates an Express router with authentication endpoints.

Options:

{
  pat: DynamoDBPat;                    // DynamoDBPat instance
  signerVerifier: JwtSignerVerifier;   // JWT signer/verifier from buildSignerVerifier()
  logger?: pino.Logger;                // Optional logger
}

Note: JWT lifetime (TTL) is configured in buildSignerVerifier().

Endpoints:

  • POST /token - Exchange PAT for JWT (OAuth 2.0 token endpoint)

    Request Body:

    {
      grant_type?: "client_credentials";  // Optional, must be "client_credentials" if provided
      client_secret?: string;             // PAT (for OAuth 2.0 client_secret_post method)
      client_id?: string;                 // Optional, accepted but not used
      state?: string;                     // Optional, echoed back in response
    }

    Authentication Methods (checked in this order):

    1. Body parameter (OAuth 2.0 client_secret_post): Include client_secret in request body
    2. Basic authentication (OAuth 2.0 client_secret_basic): Use Authorization: Basic <base64> header (format: Basic base64(":<token>"))
    3. Bearer token: Use Authorization: Bearer <token> header

    Response:

    {
      "access_token": "eyJ...", // The signed JWT
      "token_type": "Bearer", // Always "Bearer"
      "expires_in": 3600, // JWT lifetime in seconds
      "state": "..." // Optional, echoed from request
    }

Examples:

# Method 1: Body parameter (client_secret_post)
curl -X POST http://localhost:3000/auth/token \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"client_credentials","client_secret":"pat_abc123..."}'

# Method 2: Basic authentication (client_secret_basic)
curl -X POST http://localhost:3000/auth/token \
  -H "Authorization: Basic $(echo -n ":pat_abc123..." | base64)"

# Method 3: Bearer token (non-OAuth)
curl -X POST http://localhost:3000/auth/token \
  -H "Authorization: Bearer pat_abc123..."

createAdminTokensRouter(options)

Creates an Express router with admin token management endpoints. Requires JWT authentication and admin privileges.

Options:

{
  pat: DynamoDBPat;                              // DynamoDBPat instance
  signerVerifier: JwtSignerVerifier;             // JWT signer/verifier
  logger?: pino.Logger;                          // Optional logger
}

Endpoints:

  • GET /tokens - List tokens

    • Query Params: afterTokenId, limit, includeRevoked, includeExpired, includeSecretPhc, hasRole
    • Response: { "records": [...] }
  • POST /tokens - Issue a new token

    • Request Body: { "owner": "[email protected]", "isAdmin"?: false, "roles"?: ["reader"], "tokenId"?: "...", "expiresAt"?: 1234567890 }
    • Response: { "token": "pat_...", "record": {...} }
  • PUT /tokens/:tokenId - Register pre-generated token

    • Request Body: { "secretPhc": "...", "owner": "...", "isAdmin"?: false, "roles"?: ["reader"], "expiresAt"?: 1234567890 }
    • Response: { "record": {...} }
  • PATCH /tokens/:tokenId - Update token

    • Request Body: { "owner"?: "...", "isAdmin"?: true, "secretPhc"?: "...", "roles"?: ..., "expiresAt"?: 1234567890 }
    • Roles Update Syntax:
      { "roles": ["role1", "role2"] }     // Replace all roles
      { "roles": { "add": ["admin"] } }   // Atomic add (cannot combine with remove)
      { "roles": { "remove": ["guest"] } } // Atomic remove (cannot combine with add)
    • Response: 204 No Content
  • PUT /tokens/:tokenId/revoke - Revoke token

    • Request Body: { "expiresAt"?: 1234567890 } (optional)
    • Response: 204 No Content
  • PUT /tokens/:tokenId/restore - Restore revoked token

    • Response: 204 No Content
  • POST /tokens/batch - Batch retrieve tokens

    • Request Body: { "tokenIds": ["id1", "id2"], "includeSecretPhc"?: false }
    • Response: { "found": [...], "missing": [...] }

Note: All endpoints require JWT authentication and admin privileges. They use requireJwt and requireAdmin middleware internally.

Middleware

createRequireJwt(options)

Creates middleware that validates JWT tokens and populates req.user.

Options:

{
  signerVerifier: JwtSignerVerifier;   // JWT signer/verifier from buildSignerVerifier()
  logger?: pino.Logger;                // Optional logger
}

Request Extension:

req.user = {
  sub: string;      // Token ID
  owner: string;    // Token owner
  admin: boolean;   // Admin status
  roles: string[];  // Array of role strings
};

Note: The roles array comes from the JWT payload and reflects the roles assigned to the token at the time the JWT was issued.

Usage:

const requireJwt = createRequireJwt({ signerVerifier });

app.get("/protected", requireJwt, (req, res) => {
  console.log("User:", req.user?.owner);
  res.json({ data: "secret" });
});

createRequireAdmin(options?)

Creates middleware that requires req.user.admin to be true. Must be used after requireJwt.

Options:

{
  logger?: pino.Logger;  // Optional logger
}

Usage:

const requireAdmin = createRequireAdmin();

app.delete("/users/:id", requireJwt, requireAdmin, (req, res) => {
  // Only admin users can access this
  res.json({ success: true });
});

createRequireRole(options)

Creates middleware that requires req.user.roles to include a specific role. Must be used after requireJwt.

Options:

{
  role: string;            // Required role name
  logger?: pino.Logger;    // Optional logger
}

Usage:

const requireEditor = createRequireRole({ role: "editor" });

app.put("/content/:id", requireJwt, requireEditor, (req, res) => {
  // Only users with "editor" role can access this
  res.json({ success: true });
});

JWT Utilities

generateKeySet(kid: string, algorithm?: "EdDSA" | "RS256")

Generates a new asymmetric key set for JWT signing.

Parameters:

  • kid: string - Key ID (required) - unique identifier for this key set
  • algorithm?: "EdDSA" | "RS256" - Signing algorithm (default: "EdDSA")

Returns:

{
  active_kid: string;    // The active key ID
  private_keys: JWK[];   // Array of private keys in JWK format
  public_keys: JWK[];    // Array of public keys in JWK format
}

Example:

const keySet = await generateKeySet("my-key-id-1");
// or with specific algorithm
const rsaKeySet = await generateKeySet("rsa-key-1", "RS256");

Note: Store keys securely (e.g., AWS Secrets Manager, environment variables). Generate once and reuse.

buildSignerVerifier(config)

Creates JWT signer and verifier from a key set.

Config:

{
  keySet: KeySet; // From generateKeySet()
  issuer: string; // JWT issuer claim
  ttl: string; // Token time-to-live (e.g., "1h", "30m")
}

Returns:

JwtSignerVerifier {
  sign: (claims) => Promise<string>;
  verify: (jws: string) => Promise<JWTVerifyResult>;
  jwks: { keys: readonly JWK[] };
}

Example:

const keySet = await generateKeySet("my-key-id");
const signerVerifier = await buildSignerVerifier({
  keySet,
  issuer: "my-app",
  ttl: "1h",
});

OAuth 2.0 Flow

This library implements a simplified OAuth 2.0 client credentials flow:

  1. Client authenticates with PAT to POST /auth/token
  2. Server validates PAT and issues short-lived JWT (default: 1 hour)
  3. Client uses JWT for subsequent API requests via Authorization: Bearer <jwt>
  4. Server validates JWT using requireJwt middleware

Why JWT Exchange?

  • Performance: Avoid DynamoDB lookup and scrypt on every request
  • Scalability: Stateless JWT verification
  • Short-lived: Reduced risk if JWT is compromised
  • Standard: OAuth 2.0 compatible

Security Best Practices

  1. Use HTTPS - Always use TLS in production
  2. Secure Key Storage - Store private keys in secure vaults (AWS Secrets Manager, etc.)
  3. Short JWT Lifetime - Default 1 hour is recommended
  4. Rotate Keys - Implement key rotation for long-running services
  5. Validate Issuer/Audience - Configure these in production
  6. Rate Limiting - Add rate limiting to /auth/token endpoint

Key Generation

Generate keys using the included tool:

pnpm --filter @access-tokens/express genkey

Or programmatically:

import { generateKeySet } from "@access-tokens/express";

const keys = await generateKeySet("my-key-id-1");
console.log("Public Key:", keys.public_keys);
console.log("Private Key:", keys.private_keys);

Store these keys securely and pass them to your application via environment variables.

Error Handling

All endpoints return standard HTTP error codes:

  • 400 Bad Request - Invalid request body or parameters
  • 401 Unauthorized - Invalid or missing token
  • 403 Forbidden - Insufficient permissions (not admin)
  • 404 Not Found - Token not found
  • 500 Internal Server Error - Server error

Example error response:

{
  "error": "Invalid token",
  "details": "Token has been revoked"
}

TypeScript Types

The package extends Express types:

declare global {
  namespace Express {
    interface Request {
      user?: {
        sub: string; // Token ID
        owner: string; // Token owner
        admin: boolean; // Admin status
        roles: string[]; // Array of role strings
      };
      logger?: Logger; // Optional Pino logger
      clientIp?: string; // Optional client IP (from request-ip)
    }
  }
}

Requirements

  • Node.js 20+
  • Express 4.18+ or 5.0+
  • @access-tokens/core

Related Packages

License

ISC © 2025 Loan Crate, Inc.

Links