{"_id":"@autolabz/service-auth-middleware","_rev":"3-dfe0529fcebffbc46df53c94b0ba5dcb","name":"@autolabz/service-auth-middleware","dist-tags":{"latest":"0.2.2"},"versions":{"0.2.0":{"name":"@autolabz/service-auth-middleware","version":"0.2.0","_id":"@autolabz/service-auth-middleware@0.2.0","maintainers":[{"name":"autolabz","email":"mzhh@mzhh.xyz"}],"dist":{"shasum":"239cc6abe0c33e47a42c427e655e7153e42a4d6e","tarball":"https://registry.npmjs.org/@autolabz/service-auth-middleware/-/service-auth-middleware-0.2.0.tgz","fileCount":8,"integrity":"sha512-61NsDv2hZi6/YK/sD+AXMMxtAoAVZ8YBVnuar2il0wC9wvcyJR9LPlG2ALIbP/8XdI+UI2LZvn49DgPx92KL6g==","signatures":[{"sig":"MEQCIGp5dOfz6Dg0cLKPzAMAYFupIANV68BHQ5JFJTuu9YXnAiBW/lbawfLAtW+Lh8EAcBfDNw/cSuQThxr1mhwmuqGwmg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":59260},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"93935a6d544bf8cc249d07c95a7f15c6c9c8ed88","scripts":{"build":"npm run clean && tsup src/index.ts --format esm,cjs --dts --sourcemap --clean --target node18","clean":"rimraf dist","prepare":"npm run build","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"autolabz","email":"mzhh@mzhh.xyz"},"_npmVersion":"10.8.2","description":"可复用的服务端鉴权中间件集合，支持 SIMPLE（本地 JWT 验签）与 OAuth（不透明 Token，经可配置的 userinfo 接口回落校验，默认路径为 `/oauth/userinfo`）。","directories":{},"sideEffects":false,"_nodeVersion":"20.19.5","dependencies":{"jose":"^5.3.0","fastify-plugin":"^4.5.1"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","rimraf":"^5.0.5","fastify":"^4.28.1","typescript":"^5.6.3"},"peerDependencies":{"fastify":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/service-auth-middleware_0.2.0_1761233722168_0.9743333022588674","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@autolabz/service-auth-middleware","version":"0.2.1","_id":"@autolabz/service-auth-middleware@0.2.1","maintainers":[{"name":"autolabz","email":"mzhh@mzhh.xyz"}],"dist":{"shasum":"ea288cc1adb65c4fbff177c67638ce4d86499a1e","tarball":"https://registry.npmjs.org/@autolabz/service-auth-middleware/-/service-auth-middleware-0.2.1.tgz","fileCount":8,"integrity":"sha512-qx9S4qeelZI868pK+X2HATZI77kiP6eQZ7ypJNVFOTdPqh9uxvkoWup2fGVFZ+ksBxUwz1XiCH5mG2FCi8J9ZQ==","signatures":[{"sig":"MEUCIAP6b4mMeMokKcDSzYTyuWSOuZyrevpQ15bnTplmFBUyAiEA+B6rjEGiLqyVqlokRwHxl12BPLEn03RRjTbMLgjrwLk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60417},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"93935a6d544bf8cc249d07c95a7f15c6c9c8ed88","scripts":{"build":"npm run clean && tsup src/index.ts --format esm,cjs --dts --sourcemap --clean --target node18","clean":"rimraf dist","prepare":"npm run build","typecheck":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"autolabz","email":"mzhh@mzhh.xyz"},"_npmVersion":"10.8.2","description":"可复用的服务端鉴权中间件集合，支持 SIMPLE（本地 JWT 验签）与 OAuth（不透明 Token，经可配置的 userinfo 接口回落校验，默认路径为 `/oauth/userinfo`）。","directories":{},"sideEffects":false,"_nodeVersion":"20.19.5","dependencies":{"jose":"^5.3.0","fastify-plugin":"^4.5.1"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","rimraf":"^5.0.5","fastify":"^4.28.1","typescript":"^5.6.3"},"peerDependencies":{"fastify":"^4.28.1"},"_npmOperationalInternal":{"tmp":"tmp/service-auth-middleware_0.2.1_1761234902201_0.9170955105330993","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@autolabz/service-auth-middleware","version":"0.2.2","type":"module","sideEffects":false,"main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"peerDependencies":{"fastify":"^4.28.1"},"dependencies":{"fastify-plugin":"^4.5.1","jose":"^5.3.0"},"devDependencies":{"fastify":"^4.28.1","rimraf":"^5.0.5","tsup":"^8.3.0","typescript":"^5.6.3"},"scripts":{"clean":"rimraf dist","build":"npm run clean && tsup src/index.ts --format esm,cjs --dts --sourcemap --clean --target node18","prepare":"npm run build","typecheck":"tsc -p tsconfig.json --noEmit"},"_id":"@autolabz/service-auth-middleware@0.2.2","gitHead":"93935a6d544bf8cc249d07c95a7f15c6c9c8ed88","description":"可复用的服务端鉴权中间件集合，支持 SIMPLE（本地 JWT 验签）与 OAuth（不透明 Token，经可配置的 userinfo 接口回落校验，默认路径为 `/oauth/userinfo`）。","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-+zV+Z4+vUZonyE3wwuJQcar/ViwEmhbErCThDjHfI4Sv41zWaeYgIycSGAlQKJwP7+BWZUYny0e8CNVbLCL42Q==","shasum":"de94f420875d9d764a729ec98f20ddc07294853c","tarball":"https://registry.npmjs.org/@autolabz/service-auth-middleware/-/service-auth-middleware-0.2.2.tgz","fileCount":8,"unpackedSize":61029,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCFiiUY8UilHQYpopkmTORNXH02UmD3O2x/MnWkuI1urwIhAPaxz4NZxom62VPPqLNcN1HEh2XTuSFfTMgT0Zd8C33l"}]},"_npmUser":{"name":"autolabz","email":"mzhh@mzhh.xyz"},"directories":{},"maintainers":[{"name":"autolabz","email":"mzhh@mzhh.xyz"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/service-auth-middleware_0.2.2_1761300157119_0.28037605141552024"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-23T15:35:22.105Z","modified":"2025-10-24T10:02:37.583Z","0.2.0":"2025-10-23T15:35:22.339Z","0.2.1":"2025-10-23T15:55:02.415Z","0.2.2":"2025-10-24T10:02:37.417Z"},"description":"可复用的服务端鉴权中间件集合，支持 SIMPLE（本地 JWT 验签）与 OAuth（不透明 Token，经可配置的 userinfo 接口回落校验，默认路径为 `/oauth/userinfo`）。","maintainers":[{"name":"autolabz","email":"mzhh@mzhh.xyz"}],"readme":"# @shared/service-auth-middleware\n\n可复用的服务端鉴权中间件集合，支持 SIMPLE（本地 JWT 验签）与 OAuth（不透明 Token，经可配置的 userinfo 接口回落校验，默认路径为 `/oauth/userinfo`）。\n\n## 特性\n- 统一 onRequest 鉴权链：优先 SIMPLE 验签，失败则回落 OAuth userinfo。\n- OAuth 强校验：\n  - X-Client-Id 与 azp（或 userinfo.client_id）一致；\n  - 支持 requiredScopes 子集校验（空/单/多均可）。\n- 兼容：iss/aud 仅在 JWT 声明存在时校验；userinfo 回落时跳过。\n\n## 快速开始\n最简接入（使用一站式插件）：\n```ts\nimport { authPlugin } from '@shared/service-auth-middleware';\n\nconst authCfg = {\n  jwtAlg: 'HS256',\n  authBaseUrl: process.env.AUTH_BASE_URL!,\n  oauthUserinfoPath: 'oauth/userinfo',\n  oauthUserinfoTimeoutMs: Number(process.env.OAUTH_USERINFO_TIMEOUT_MS || 2000),\n} as const;\n\napp.register(authPlugin, {\n  authConfig: authCfg,\n  clientId: {},\n  enforce: { requiredScopes: ['data'] },// scope 校验，比如开发一个data-service就对客户端要求data的scope，可为空。\n});\n```\n\n## 安装与构建\n在 monorepo 根目录：\n```bash\nnpm --workspace shared/service-auth-middleware run build\n```\n\n或在 Docker 多阶段构建中先构建该包，再构建依赖它的服务。\n\n## 使用（Fastify）\n```ts\nimport { oauthOrSimpleAuth, clientIdMiddleware, oauthEnforceClientScope, authPlugin, makeAuthBridgeFromRequest } from '@shared/service-auth-middleware';\nimport { createPointsClient } from '@autolabz/points-sdk';\n\nconst authCfg = {\n  // SIMPLE 模式（本地 JWT 验签）相关：\n  jwtAlg: 'HS256',                       // SIMPLE 模式必须指定算法（HS256/RS256）\n  jwtAccessSecret: process.env.JWT_ACCESS_SECRET, // 当 jwtAlg=HS256 时用于本地验签\n  jwksUrl: process.env.JWKS_URL,         // 当 jwtAlg=RS256 时用于远程公钥集\n  authIssuer: process.env.AUTH_ISSUER,   // SIMPLE 模式下对 iss 的可选强校验\n\n  // OAuth userinfo 回落相关：\n  authBaseUrl: process.env.AUTH_BASE_URL!,           // 必填：OAuth 基础 URL\n  oauthUserinfoPath: 'oauth/userinfo',              // 必填（如不自定义则使用默认路径）\n  oauthUserinfoTimeoutMs: Number(process.env.OAUTH_USERINFO_TIMEOUT_MS || 2000), // 必填（可用默认 2000）\n\n  // 预留：未来扩展 aud 校验（可选）\n  oauthExpectedAudience: process.env.OAUTH_EXPECTED_AUDIENCE,\n} as const;\n\napp.register(authPlugin, {\n  authConfig: authCfg,\n  clientId: {},\n  enforce: { requiredScopes: ['data'] },\n});\n\n// 在需要调用下游服务（如 points/data/llmapi）的路由中，构造 AuthBridge\napp.get('/v1/points/my-balance', async (req, reply) => {\n  const auth = makeAuthBridgeFromRequest(req, {\n    onUnauthorized: () => {\n      // 可选：记录日志/打点\n    },\n  });\n  // 传入到 SDK（示例：@autolabz/points-sdk）\n  const points = createPointsClient({ baseURL: process.env.POINTS_BASE_URL!, auth });\n  const result = await points.getMyBalance();\n  return reply.send(result);\n});\n```\n\n## 客户端访问（AuthBridge）\n\n服务作为“客户端”访问下游服务时，可用 `makeAuthBridgeFromRequest` 从上游请求提取 `Authorization` 与 `X-Client-Id`，透明透传到下游，从而复用同一套鉴权链与 scope 约束。\n\n- 对接 SDK（示例：`@autolabz/points-sdk`）\n```ts\nconst auth = makeAuthBridgeFromRequest(req, {\n  onUnauthorized: () => req.log.warn('downstream unauthorized'),\n});\n\nconst points = createPointsClient({\n  baseURL: process.env.POINTS_BASE_URL!,\n  auth,\n});\n\nconst balance = await points.getMyBalance();\n```\n\n- 搭配任意 HTTP 客户端（以 fetch 为例）\n```ts\nconst auth = makeAuthBridgeFromRequest(req);\n\nasync function buildAuthHeaders(extra?: Record<string, string>) {\n  const [token, clientId] = await Promise.all([\n    Promise.resolve(auth.getAccessToken()),\n    Promise.resolve(auth.getClientId()),\n  ]);\n  return {\n    'Content-Type': 'application/json',\n    ...(extra || {}),\n    Authorization: token ? `Bearer ${token}` : '',\n    'X-Client-Id': clientId ?? '',\n  } as Record<string, string>;\n}\n\n// 模仿 SDK：注入头 + 401 刷新后重试一次\nasync function fetchWithAuth(url: string, init: RequestInit = {}) {\n  const first = await fetch(url, {\n    ...init,\n    headers: await buildAuthHeaders(init.headers as any),\n  });\n  if (first.status !== 401) return first;\n\n  try {\n    const newAccessToken = await Promise.resolve(auth.refreshAccessToken());\n    const retryHeaders = await buildAuthHeaders({\n      ...(init.headers as any),\n      Authorization: newAccessToken ? `Bearer ${newAccessToken}` : '',\n    });\n    return await fetch(url, { ...init, headers: retryHeaders });\n  } catch (_e) {\n    try { auth.onUnauthorized?.(); } catch {}\n    return first;\n  }\n}\n\n// 使用示例\nconst res = await fetchWithAuth(`${process.env.DATA_BASE_URL}/v1/data/items`);\nconst data = await res.json();\n```\n\n## 简化示例（仅使用 userinfo 回落）\n\n当你只依赖 OAuth 的 userinfo 回落（不做本地 JWT 验签）时：\n- **必填（OAuth userinfo）**：`authBaseUrl`、`oauthUserinfoPath`、`oauthUserinfoTimeoutMs`\n- **占位必填（SIMPLE 关闭）**：`jwtAlg`（任选 `'HS256' | 'RS256'` 以满足类型）；无需提供 `jwtAccessSecret` / `jwksUrl`\n- **可不填**：`jwtAccessSecret`、`jwksUrl`、`authIssuer`、`oauthExpectedAudience`（预留）\n- **requiredScopes**：当为空或未传时，跳过 scope 校验；传入多个时要求子集关系（都必须包含）。\n- **enforceForSimple**：如需对 SIMPLE 也校验 scope，可设置 true（通常不需要）。\n\n## 配置项参考（AuthConfig）\n\n| 键 | 说明 | 必填 | 默认值 |\n| --- | --- | --- | --- |\n| jwtAlg | SIMPLE 模式算法：'HS256' | 'RS256'。仅使用 userinfo 时作为占位满足类型 | 是（SIMPLE 或占位） | - |\n| jwtAccessSecret | HS256 本地验签密钥 | 当 jwtAlg=HS256 时必填 | - |\n| jwksUrl | RS256 JWK Set 地址 | 当 jwtAlg=RS256 时必填 | - |\n| authIssuer | iss 强校验值（仅在 JWT 声明存在时校验） | 否 | - |\n| authBaseUrl | OAuth 基础 URL（例如认证服务外部可达地址） | 是（使用 userinfo 时） | - |\n| oauthUserinfoPath | userinfo 路径 | 是（使用 userinfo 时） | /oauth/userinfo |\n| oauthUserinfoTimeoutMs | userinfo 请求超时（毫秒） | 是（使用 userinfo 时） | 2000 |\n| oauthExpectedAudience | 预留：aud 期望值 | 否 | - |\n\n## 环境变量与网关建议\n- 建议在网关层剥离外部传入的 `X-Client-Id` 并由后端重建，避免伪造。\n- `AUTH_BASE_URL` 应为认证服务外部可达地址（在 Docker 网络中可用服务名或网关暴露地址）。\n\n常用环境变量映射：\n\n| 变量 | 作用 | 示例 |\n| --- | --- | --- |\n| JWT_ALG | SIMPLE 模式算法 | HS256 |\n| JWT_ACCESS_SECRET | HS256 本地验签密钥 | your-secret |\n| JWKS_URL | RS256 JWK Set 地址 | http://auth/.well-known/jwks.json |\n| AUTH_ISSUER | iss 校验值 | https://auth.example.com |\n| AUTH_BASE_URL | OAuth 基础 URL | http://auth-service:4001 |\n| OAUTH_USERINFO_PATH | userinfo 路径 | /oauth/userinfo |\n| OAUTH_USERINFO_TIMEOUT_MS | userinfo 超时（ms） | 2000 |\n| OAUTH_EXPECTED_AUDIENCE | 预期 aud | autolab-api |\n\n## 返回字段\n- req.auth: { userId, sub?, email?, iss?, aud?, azp?, scope?, tokenType? }\n- req.clientId: 解析自 X-Client-Id 或查询参数 client_id。\n\n## 迁移自定义校验\n- 若不同路由需要不同 scopes，可在该路由的 preHandler 再挂一次 oauthEnforceClientScope(authCfg, { requiredScopes: ['xxx'] }).\n\n## 常见问题与排查\n- 401（userinfo 校验失败）：\n  - 检查 `AUTH_BASE_URL` 与 `OAUTH_USERINFO_PATH` 是否正确；\n  - 确认 Authorization 头使用 `Bearer <token>`；\n  - 核对所需 scope 是否包含于 access token。\n- X-Client-Id 与 azp 不一致：\n  - 确认网关未将外部的 `X-Client-Id` 透传；由后端统一设置或重建此头。\n- 请求超时：\n  - 调整 `OAUTH_USERINFO_TIMEOUT_MS`；检查认证服务的 userinfo 性能与网络连通性。\n- 本地 JWT 验签失败：\n  - HS256：检查 `JWT_ACCESS_SECRET` 是否一致；RS256：检查 `JWKS_URL` 可访问且 kid 对应。\n\n## 许可\nMIT\n","readmeFilename":"README.md"}