Skip to content

Token 实现指南(开发者视角)

读者画像:在仓内贡献代码的全栈/后端工程师,写新端点时想知道"挑哪个认证中间件 / 凭证怎么撤销 / 401 vs 403 vs 429 怎么区分"。

1. 视角说明(开篇必读)

本文档与 /api/authentication 互补,不冲突——两者按不同维度切分同一套认证体系:

文档切分维度分类
/api/authentication端点适配(端点接受哪种 AuthorizationJWT / API Key / Schedule / OAuth
本文(/dev/token-types凭证主体生命周期(颁发 / 校验 / 撤销)Supabase JWT / Agent Token / API Key / OAuth (access + refresh)

Schedule Token 与 Internal Secret 不属于独立"凭证"主体——前者由系统派发给定时任务、后者用于仓内服务间通信。但开发者写新端点时仍可能遇到,故在 §3 末尾以"非主轴凭证"补充。


2. 四种凭证总览

凭证主体颁发流程入口寿命典型场景不该用于
Supabase JWTUserSupabase Auth(@supabase/auth-js1h(自动刷新)浏览器会话长寿命服务调用
Agent Token(wna- 前缀)Team Agent(agent_bindings创建 Agent 时随响应一次性返回;后续补发 / 轮换走 POST /teams/:teamId/agents/:bindingId/issue-token(Team Owner)永久(手动吊销)Agent / 长寿命设备直接给最终用户使用
API Key(wn- 前缀)Team / 用户POST /teams/:teamId/api-keys(Team Owner)可配置 expires_at + 软撤销第三方系统集成浏览器会话
OAuth Token(wno- access + refresh)OAuth App + UserPOST /oauth/tokenaccess 1h / refresh 30d第三方应用代用户调用仓内服务间通信

实现源码:

  • 工具函数:apps/backend/src/utils/agent-token.tsapps/backend/src/utils/oauth.tsapps/backend/src/utils/schedule-token.ts
  • 数据库迁移:supabase/sql/20260313_agent_token_permanent.sqlsupabase/migrations/20260321_oauth_authorization_server.sqlsupabase/migrations/20260404_api_permission_groups.sqlsupabase/migrations/20260404_permission_rpc_functions.sql

3. 中间件选型矩阵

写新端点时的中间件选择决策表:

端点性质推荐中间件文件
用户专属(个人凭证管理、设置)jwtAuth(仅 JWT)apps/backend/src/middleware/jwt-auth.ts
通用业务接口(用户 + API Key 都能调)combinedAuthapps/backend/src/middleware/combined-auth.ts
已发布应用的运行时(OAuth + API Key + scope 校验)oauthAuth + oauthScopeGuard + apiKeyAuthapps/backend/src/middleware/oauth-auth.tsapps/backend/src/middleware/api-key-auth.ts
运行记录读 / 事件 / resume / cancel(JWT / API Key / Agent / OAuth)selectAuth + requireRunAccessapps/backend/src/middleware/run-auth.ts
API Key 专属端点apiKeyAuthapps/backend/src/middleware/api-key-auth.ts(内部支持 wn- / wna- 前缀分发,并兼容 Schedule JWT)

选型决策建议

  1. 能用 combinedAuth 就别单挂 jwtAuth——前者覆盖后者的能力且不阻挡 API Key 集成
  2. 运行时(runs / events / resume / cancel)一律走 selectAuth + requireRunAccess——它会按 run 上下文统一校验四种凭证的访问权
  3. OAuth 端点必须 oauthScopeGuard——只校验认证身份不校验 scope 等于绕过授权
  4. 新端点默认不挂 apiKeyAuth 单独使用,除非明确是已发布应用入口

非主轴凭证(写新端点请知悉)

  • Schedule Token:仅在直接挂载 apiKeyAuth 的运行时路由中生效,由 apiKeyAuth 的 Bearer JWT 分支调用 verifyScheduleToken() 识别(参见 apps/backend/src/middleware/api-key-auth.ts:118-135);不经过 combinedAuth,也不通过 selectAuth + requireRunAccess 的 HTTP 访问链暴露
  • Internal Secret:仅在 apiKeyAuth 中通过 X-Internal-Secret + X-Internal-Api-Key-Id 旁路处理,限仓内 release_api webhook 投递等内部调用使用;新端点默认不要复用此通道

4. 凭证撤销机制(按类型分述)

每种凭证的撤销字段不同,必须分别讨论。

4.1 API Key

  • 机制:软删除 api_keys.deleted_at 或软撤销 api_keys.revoked_at,认证中间件(apps/backend/src/middleware/authenticate-api-key.ts)对两者都拒收
  • 当前实现:前端通过 Supabase RPC delete_api_key(api_key_id) 直接调用(不经过 REST 路由),写入 deleted_at
  • REST 撤销端点:当前不存在,待 F3 子任务补全(具体路径由 F3 plan 决定,建议保持 team 资源口径如 DELETE /teams/:teamId/api-keys/:apiKeyId,本文档不预先定路径)

4.2 Agent Token(区分三类操作!)

Agent 域内有 3 个看似接近、实际语义完全不同的操作,写代码时不要混淆

操作端点 / 实现数据库写入效果
吊销 Token(Agent 本体仍存在)POST /teams/:teamId/agents/:bindingId/revoke-token(参见 apps/backend/src/routes/team-agents.ts:730-768agent_bindings.token_hash = null旧 Token 立即失效,Agent 仍可被重新签发
禁用发送权UPDATE 接口(无独立路由)agent_bindings.can_send = falseAgent 还在、Token 还在,但运行时拒绝发送
删除 AgentDELETE /teams/:teamId/agents/:bindingId(参见 apps/backend/src/routes/team-agents.ts:822-849agent_bindings.deleted_at = NOW()整个 Agent 软删除,所有挂在它上面的 Token 一并失效
补发 / 轮换 Token(覆盖旧)POST /teams/:teamId/agents/:bindingId/issue-token原子替换 agent_bindings.token_hash老 Token 立即失效,明文新 Token 仅返回一次(创建 Agent 已包含一次明文返回,本端点用于错过或定期轮换)

4.3 OAuth Token

  • Token 级POST /oauth/revoke(RFC 7009),写入 oauth_tokens.revoked_at = NOW()(实现见 apps/backend/src/routes/oauth.ts
  • App 级oauth_apps.disabled_at 禁用整个 OAuth App,所有该 App 颁发的 access / refresh token 全部失效

4.4 Supabase JWT

  • 无独立撤销端点,依赖 Supabase Auth 自身的 session 失效机制(用户登出 / refresh token 过期 / 后端通过 Admin API 强制登出)

4.5 Cache 与失效时延(容易踩坑)

读这一节区分两层不同的缓存语义,避免误以为撤销有 30s 延迟:

检查项是否走 cache失效时延
凭证状态(token_hashrevoked_atdeleted_atexpires_atoauth_tokens.revoked_at直查 DB(中间件无 token cache)撤销立即生效
路由权限分组(permission_groups,参见 apps/backend/src/services/permission.service.tstokenCache + permission_version 30s TTL配置变更最多 30s

简单说:撤销凭证立即生效,调整路由权限分组配置最多 30s 后生效。两者互不影响。


5. 跨端凭证派发流程

场景:用户在 Web 登录后,需要为长寿命设备 / Agent 派发 Token 让另一端使用。

入口

UI 入口在 /o/:teamSlug/agents(团队 Agent 管理页),不是 Settings/Devices。前端服务封装在 apps/frontend/src/services/agentService.ts

流程(时序)

关键点:创建 Agent 时 backend 一次性生成并直接返回明文 Token(参见 apps/backend/src/routes/team-agents.tsPOST /teams/:teamId/agents 实现,约 440-478 行;前端入口 apps/frontend/src/services/agentService.tsagentService.create())。POST .../issue-token 只在错过明文或主动轮换时使用,会覆盖已有 token_hash,使旧明文立即失效。

Team Owner          Web (agentService)        Backend                  Agent Device
    │                      │                      │                          │
    │ 1. 创建 Agent        │                      │                          │
    │─────────────────────▶│                      │                          │
    │                      │ POST /teams/:id/agents                          │
    │                      │─────────────────────▶│                          │
    │                      │                      │ INSERT agent_bindings    │
    │                      │                      │ 同时生成 wna- token       │
    │                      │                      │ 存 token_hash             │
    │                      │ ◀── { agent, token } │                          │
    │                      │   (明文仅返回一次!)   │                          │
    │ 2. 复制到设备        │                      │                          │
    │  ─────────────────── │ ────────────────────  │ ─────────────────────▶ │
    │                      │                      │                          │
    │                      │                      │  Agent 用 Token 调 API   │
    │                      │                      │ ◀────────────────────────│
    │                      │                      │  selectAuth + run-auth   │
    │                      │                      │  校验 token_hash         │
    │                      │                      │ ─────────────────────▶  │
    │                      │                      │                          │
    │ 3. 补发 / 轮换       │                      │                          │
    │   (错过明文或定期轮换)                                                │
    │─────────────────────▶│ POST issue-token     │                          │
    │                      │─────────────────────▶│ 生成新 token,            │
    │                      │                      │ 原子覆盖 token_hash      │
    │                      │ ◀── 新明文 token     │                          │
    │                      │   (旧 token 立即失效) │                          │
    │                      │                      │                          │
    │ 4. 吊销(可选)      │                      │                          │
    │─────────────────────▶│ POST revoke-token    │                          │
    │                      │─────────────────────▶│ token_hash = null        │
    │                      │                      │  (Agent 调用立即 401)  │

安全提醒

  • 明文 Token 只在 create / issue-token 响应里出现一次,前端不能持久化、必须由用户立即复制到目标设备
  • 错过明文 → 调 agentService.issueToken() 补发,旧 Token 立即失效
  • 任何进入日志 / 监控的位置都不应出现明文 Token,仅可输出 token_hash 前缀

6. 错误码与排错

HTTP含义触发原因实现位置
401 Unauthorized凭证无效过期 / 已撤销 / 格式错 / token_hash 已置空 / Schedule JWT 校验失败各 auth 中间件
403 Forbidden(权限类)凭证有效但无权限scope 缺失 / Team 不匹配 / Agent 无 can_sendOAuth scope guard、role 检查
403 API key credit limit exceededAPI Key 积分耗尽(usage_credits >= limit_creditsapps/backend/src/middleware/authenticate-api-key.ts:138-142API Key 认证
429 Too Many Requests请求频率限流(RPS)Rate limiterrate-limit middleware(如已挂载)+ X-RateLimit-* headers

403 vs 429 别混淆:403 是"积分余额耗尽"(账面问题,需要充值或扩限额);429 是"调用过快"(速率问题,等几秒重试或退避)。前者在响应体的 error 字段中明确说明 credit limit exceeded,后者在 headers 中携带 Retry-After

排错速查

客户端表现后端响应示例应做的事
401{ "error": "Invalid authorization token" }检查 Token 是否被撤销 / 过期;若是 Agent,确认 revoke-token 是否被误调用
401{ "error": "API key has expired" }重新签发 API Key(或调整 expires_at
403{ "error": "API key credit limit exceeded" }调高 limit_credits 或等周期重置
403{ "error": "insufficient_scope" }OAuth Token 缺少所需 scope,重新申请授权
403{ "error": "Only team owner can ..." }调用方不是 Team Owner,换账号或调整 Team 角色
429{ "error": "Too Many Requests" } + Retry-After header实现退避重试,降低并发

参考

  • 认证端点视角文档:/api/authentication
  • OAuth 完整接入:/api/oauth/guide/oauth/oauth/callback
  • 后端中间件:apps/backend/src/middleware/jwt-auth.tsapps/backend/src/middleware/api-key-auth.tsapps/backend/src/middleware/oauth-auth.tsapps/backend/src/middleware/combined-auth.tsapps/backend/src/middleware/run-auth.ts
  • API Key 校验实现:apps/backend/src/middleware/authenticate-api-key.ts
  • OAuth 路由:apps/backend/src/routes/oauth.ts
  • Agent 路由:apps/backend/src/routes/team-agents.ts
  • 前端 Agent 服务:apps/frontend/src/services/agentService.ts
  • 权限缓存:apps/backend/src/services/permission.service.ts

本文档面向仓内开发者。如果你是接入外脑的第三方系统,请参考 /integration/authentication

最后更新于:

AI Workflow Editor