Skip to content

Auth Middleware

@vafast/auth-middleware 对接独立 auth-server,提供 JWT / API Key 硬认证app-id 校验、守卫与带类型的路由定义器。

硬认证含义:挂了中间件就必须验证成功;失败直接返回错误响应,没有「验证失败当作游客继续」的预配置路径。需要认证就挂,不需要就不挂。

先搞清几个概念(给新用户)

Bearer JWT vs API Key

客户端都走同一个请求头:

http
Authorization: Bearer <凭证>

中间件用 凭证里是否包含冒号 : 区分两种方式:

方式凭证形态行为
JWT不含 : 的 token 字符串调 auth-server /verifyJwt;可选把请求头 app-id 一并传入
API KeyapiKeyId:secretKey(含冒号)/verifyApiKey;注入 userInfo + apiKey

因此:

  • jwtAuth / authenticateJwt:若 token 含 :,直接 401「无效的 JWT Token」
  • apiKeyAuth / authenticateApiKey:若 token 不含 :,直接 401「无效的 API Key 格式」
  • auth / authenticate / authWithApp:自动按是否含 : 分支

注意:这里的 Bearer API Key(用户/调用方凭证)与服务进程调用 auth-server 时用的 AUTH_SERVICE_API_KEY_ID / SECRET(服务间通信)是两套东西。

app-id 与多租户

多租户接口通常要求请求头:

http
app-id: <appId>
中间件作用
appValidator / validateApp校验 app-id,注入 app
authWithApp / authenticateWithApp用户认证 校验 app-id,一次注入 userInfo + app(API Key 时还有 apiKey

JWT 路径下,authWithApp 会把 app-id 传给 verifyJwt;若返回的用户信息没有 app,按无效 app-id 处理(400)。
API Key 路径下会再调一次 verifyApp(appId)

若上下文里已有匹配的 app(或 userInfo.app),validateApp / appValidator 会复用,避免重复请求。

失败码:401 vs 400

本包刻意区分「身份问题」与「租户 / 应用问题」:

场景典型状态码说明
缺少 / 非法 Authorization401未提供认证信息、无效 JWT / API Key、账号禁用/删除等
JWT / API Key 校验失败401auth-server 错误码若在 400–599 会透传,否则回落 401
缺少 app-id(required)400如「缺少必需的请求头: app-id」
app-id 无效400默认回落 400;auth-server 返回的 4xx/5xx code 可透传
守卫:无 userInfo / 无 apiKey401requireUser / requireApiKey
守卫:无 app400requireApp
守卫:requireUserAndApp先查用户 401,再查 app 400
auth-server 超时504 风格文案:认证服务响应超时…
auth-server 不可用(网络等)503 风格文案:认证服务暂时不可用…

实现细节:用户认证失败多用 throw err(...);缺少/无效 app-id 多用 Response.json({ code, message }, { status })。对调用方而言都是对应 HTTP 状态。

安装

bash
npm install @vafast/auth-middleware

快速开始

配置环境变量后直接使用预配置中间件:

bash
AUTH_API_BASE_URL=http://localhost:9003
AUTH_SERVICE_API_KEY_ID=ak_xxx
AUTH_SERVICE_API_KEY_SECRET=sk_xxx
# 可选:AUTH_API_TIMEOUT=5000
typescript
import { Server, defineRoute, defineRoutes, serve } from 'vafast'
import {
  authWithApp,
  requireUser,
  defineAuthRouteWithApp,
} from '@vafast/auth-middleware'

const routes = defineRoutes([
  defineRoute({
    path: '/api/files',
    middleware: [authWithApp],
    children: [
      defineAuthRouteWithApp({
        method: 'GET',
        path: '/list',
        middleware: [requireUser],
        handler: ({ userInfo, app }) => ({
          userId: userInfo.id,
          appId: app.id,
        }),
      }),
    ],
  }),
])

const server = new Server(routes)
serve({ fetch: server.fetch, port: 3000 })

用法

环境变量

变量必需说明
AUTH_API_BASE_URL是*auth-server 基础 URL
AUTH_SERVICE_API_KEY_ID服务间 API Key ID
AUTH_SERVICE_API_KEY_SECRET服务间 API Key Secret
AUTH_API_TIMEOUT超时毫秒,默认 5000

* createAuthClient / 懒加载预配置中间件在缺少 baseUrl(参数与环境变量都没有)时会抛错。

请求头

Header说明
Authorization: Bearer <jwt>JWT(token 中不能含 :
Authorization: Bearer <apiKeyId>:<secret>API Key(必须含冒号)
app-id: <appId>多租户 / validateApp / authWithApp

预配置中间件(懒加载单例)

首次请求时 createAuthClient() 读环境变量,之后复用同一客户端。

导出行为注入上下文
authJWT + API Key 自动识别userInfo,API Key 时另有 apiKey
jwtAuth仅 JWT(含 : 视为非法)userInfo
apiKeyAuth仅 API KeyuserInfo + apiKey
appValidator必需 app-id 并网络校验app
authWithApp用户认证 + app(最常用)userInfo + app(API Key 时含 apiKey
typescript
import { auth, appValidator, authWithApp } from '@vafast/auth-middleware'

middleware: [auth, appValidator]
// 或一步到位
middleware: [authWithApp]

工厂函数(可传配置 / 共享客户端)

typescript
import {
  createAuthClient,
  authenticate,
  authenticateJwt,
  authenticateApiKey,
  validateApp,
  authenticateWithApp,
} from '@vafast/auth-middleware'

const client = createAuthClient({
  baseUrl: 'http://127.0.0.1:9003',
  apiKeyId: 'xxx',
  apiKeySecret: 'yyy',
})

const authMw = authenticate(client) // 或 authenticate({ baseUrl: '...' }) / authenticate()
const jwtOnly = authenticateJwt()
const keyOnly = authenticateApiKey()
const appMw = validateApp(undefined, { required: false, verify: true })
const both = authenticateWithApp(client)
工厂语义化别名说明
authenticateauthJwtAndApiKeyJWT + API Key
authenticateJwtauthJwt仅 JWT
authenticateApiKeyauthApiKey仅 API Key
validateAppvalidateAppId校验 app-id
authenticateWithAppauthApp用户 + app

AuthMiddlewareOptions = AuthClientConfig | AuthClient | undefined

validateApp(config?, options?)options

字段默认说明
requiredtrue缺少 / 无效 app-id 时是否拒绝
verifytruefalse 时只检查 header,注入最小化 { id, name: '', status: 'active' }

守卫(不发网络请求)

只检查 __locals 上下文:

守卫条件失败
requireUseruserInfo401「未登录或用户信息缺失」
requireAppapp400「缺少有效的 app-id」
requireApiKeyapiKey401「无效的 API Key」
requireUserAndApp同时有 userInfo + app缺用户 401 / 缺 app 400
typescript
middleware: [auth, appValidator, requireUserAndApp]

典型组合:路由组挂 authWithApp(或 auth + appValidator),叶子再挂 requireUser 等做类型与运行时双保险。

路由定义器

基于 withContext,零运行时开销,补齐 handler 类型;内置 RouteExtensions.webhook

定义器Handler 上下文
defineAuthRoute{ userInfo: UserInfo }
defineOptionalAuthRoute{ userInfo?: UserInfo }
defineApiKeyRoute{ userInfo?, apiKey? }
defineAuthRouteWithApp{ userInfo, app }
defineRouteWithApp{ app }
defineOptionalAuthRouteWithApp{ userInfo?, app }
defineFullAuthRoute{ userInfo, apiKey?, app }
typescript
defineAuthRouteWithApp({
  method: 'POST',
  path: '/create',
  name: '创建资源',
  webhook: true, // 或 { eventKey, include, exclude }
  middleware: [requireUser],
  handler: ({ userInfo, app }) => ({ userId: userInfo.id, appId: app.id }),
})

RouteExtensions.webhookboolean | { eventKey?, include?, exclude? }(比 @vafast/webhook 完整配置更精简;condition / transform 需用 webhook 包的类型扩展)。

Auth Client

typescript
const client = createAuthClient()

await client.verifyJwt(token, appId?)
await client.verifyApiKey(apiKeyId, secretKey)
await client.verifyApp(appId)
await client.getUsersBatch(userIds, options?)
await client.searchUsers({ keyword, appId, current, pageSize }, options?)
await client.getUsersStats({ appId, startTime, endTime }, options?)

超时 / 网络错误归一为 504 / 503 风格错误信息;JWT 禁用 / 删除账号返回 401。

API

createAuthClient(config?)

字段类型默认说明
baseUrlstringAUTH_API_BASE_URL缺则抛错
apiKeyIdstringAUTH_SERVICE_API_KEY_ID服务间 Bearer
apiKeySecretstringAUTH_SERVICE_API_KEY_SECRET与 ID 组成 id:secret
timeoutnumberAUTH_API_TIMEOUT5000毫秒

UserInfo 与组织相关字段

typescript
interface UserInfo {
  id: string
  appId: string
  email?: string
  phone?: string
  avatar?: string
  status?: string
  roleId?: string
  nickname?: string
  verified?: boolean
  /** 组织成员所属组织。组织成员身份直接归属 organization。 */
  organizationId?: string
  /** organization_member 表示组织成员,customer_user 表示客户应用终端用户。 */
  accountType?: string
  /** 组织成员跨 app 访问时为 true,用于区分后台账号和目标 app 终端用户身份 */
  isOrgMemberAccess?: boolean
  /** 本次请求 app-id 指向的目标 app,跨 app 访问时与用户原始 appId 不同 */
  targetAppId?: string
  /** 本次访问模式:direct 为当前 app 用户直接访问,org_member_delegate 为组织成员代理访问。 */
  accessMode?: 'direct' | 'org_member_delegate'
}
字段含义(与源码注释一致)
organizationId组织成员所属组织;组织成员身份直接归属 organization
accountTypeorganization_member = 组织成员;customer_user = 客户应用终端用户
isOrgMemberAccess组织成员跨 app 访问时为 true,用于区分后台账号与目标 app 终端用户
targetAppId本次请求 app-id 指向的目标 app;跨 app 时与用户原始 appId 不同
accessModedirect:当前 app 用户直接访问;org_member_delegate:组织成员代理访问

VerifiedUserInfoUserInfo 基础上可带 app?: AppInfo(JWT 校验结果可能附带已验证的 app 摘要)。

其它上下文类型

typescript
interface ApiKeyInfo {
  id: string
  name: string
  appId: string
  userId: string
  status: string
  permissions?: string[]
}

interface AppInfo {
  id: string
  name: string
  status: string
  /** auth-server 维护的 app 结构化扩展,如 organizationId */
  extensions?: Record<string, unknown>
}

导出一览

  • 客户端:createAuthClientAuthClientAuthClientConfig
  • 预配置:authjwtAuthapiKeyAuthappValidatorauthWithApp
  • 工厂:authenticateauthenticateJwtauthenticateApiKeyvalidateAppauthenticateWithApp 及别名
  • 守卫:requireUserrequireApprequireApiKeyrequireUserAndApp
  • 路由定义器:上表 7 个
  • 类型:UserInfoVerifiedUserInfoApiKeyInfoAppInfoApiKeyContextValidateAppContextAuthWithAppContextValidateAppOptionsRouteExtensionsWebhookConfigOptions

最佳实践

  • 路由组挂 authWithApp,叶子挂 requireUser 等守卫做断言。
  • 公开接口不挂认证中间件。
  • 多中间件共享同一 createAuthClient() 实例,避免重复配置。
  • 全局放 cors / requestId;认证放路由组,不要全局强制登录。
  • 需要区分终端用户与组织成员代理访问时,读取 accessMode / isOrgMemberAccess / targetAppId,不要只看 userInfo.id

注意事项

  • 硬认证:挂了就必须成功;没有「软登录失败继续」的预配置中间件(可选上下文靠「不挂 auth」或自建逻辑)。
  • 预配置中间件依赖进程环境变量;测试或其它 baseUrl 请用工厂 + 显式 createAuthClient
  • authenticateJwt / jwtAuth 拒绝带 : 的 token;authenticateApiKey / apiKeyAuth 要求 id:secret
  • 用户凭证失败多为 401app-id 缺失/无效多为 400。不要混用语义。
  • auth-server 超时返回类 504 文案;不可用类 503。
  • 包内 webhook 类型扩展仅便于声明;实际分发需安装并 server.use(webhook(...))

相关链接