@access-tokens/express

中文文档
1.1.0发布于 2025/12/31ISCTypeScriptCommonJS构建来源已验证近 30 天下载 1.5万

Express routes and middleware for personal access token authentication

原始使用文档
文档目录17 节
  1. Features
  2. Installation
  3. Quick Start
  4. API Reference
  5. Routes
  6. Middleware
  7. JWT Utilities
  8. OAuth 2.0 Flow
  9. Why JWT Exchange?
  10. Security Best Practices
  11. Key Generation
  12. Error Handling
  13. TypeScript Types
  14. Requirements
  15. Related Packages
  16. License
  17. Links

@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

License

ISC © 2025 Loan Crate, Inc.

版本与发布记录

@access-tokens/express 共发布 3 个版本,首次发布于 2025/11/07;过去 12 个月发布了 2 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.1.0latest2025/12/3110 个月前
1.0.12025/11/0811 个月前
1.0.02025/11/071 年前

关于 @access-tokens/express 的常见问题

@access-tokens/express 是什么?

Express routes and middleware for personal access token authentication

如何安装 @access-tokens/express?

在项目目录运行 npm install @access-tokens/express,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:express(^4.18.0 || ^5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@access-tokens/express 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@access-tokens/express 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@access-tokens/express 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@access-tokens/express 可以商用吗?

@access-tokens/express 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@access-tokens/express 的下载量有多少?

根据 npm 官方下载统计,@access-tokens/express 在 2026/10/05 – 2026/11/03 这 30 天内被下载 14,660 次,日均约 489 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@access-tokens/express 还在维护吗?

最新版本 1.1.0 发布于 2025/12/31(10 个月前);至今共发布 3 个版本,其中过去 12 个月发布了 2 个;首次发布于 2025/11/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。