Files
Genarrative/docs/technical/OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

10 KiB
Raw Blame History

OIDC JWT Claims 设计

日期:2026-04-21

1. 文档目的

这份文档用于完成 M2 中的两条任务:

  1. 设计 JWT claims
  2. 确认 iss/sub/sid/provider/roles 字段

目标是把当前 Node 后端只包含 sub + ver 的轻量 JWT升级为一份既兼容 Axum Bearer 鉴权、又可用于 SpacetimeDB 身份透传的 OIDC 风格 claims 设计,并固定:

  1. 必须出现的标准字段与扩展字段
  2. 哪些字段属于 access token哪些不应该塞进 JWT
  3. Axum、platform-authmodule-authSpacetimeDB 各自如何使用这些 claims
  4. 与当前 Node token 口径的映射方式

2. 当前基线

当前 Node token 实现位于:

  1. server-node/src/auth/token.ts
  2. server-node/src/middleware/auth.ts
  3. server-node/src/types/express.d.ts

当前 Node access token 口径:

  1. Header
    • alg = HS256
    • typ = JWT
  2. 标准字段:
    • sub = userId
    • iss = config.jwtIssuer
    • iat
    • exp
  3. 自定义字段:
    • ver = tokenVersion

当前主要问题:

  1. claims 信息过薄,不足以直接支撑 SpacetimeDB 侧的身份上下文判断
  2. sid/provider/roles 等字段尚未冻结,后续 platform-authmodule-auth 实现没有稳定目标
  3. 当前 Express request.auth 里也只有 userIdtokenVersion

3. 设计目标

新的 access token 必须同时满足:

  1. Axum 可直接做 Bearer 校验与最小授权判断
  2. SpacetimeDB 可识别发行者与稳定主体身份
  3. 后续 module-auth 可基于 sid/provider/roles 做会话与能力判定
  4. 前端无需解析大量敏感信息
  5. claims 尺寸保持克制,不把会话明细或风控状态塞进 JWT

4. token 分层

当前阶段固定只设计两类 token

4.1 Access Token

用途:

  1. Authorization: Bearer <token>
  2. Axum 路由鉴权
  3. SpacetimeDB 身份透传

特点:

  1. 短期有效
  2. 带完整最小 claims
  3. 可在服务间透传

用途:

  1. 浏览器长期登录续期
  2. 轮换 access token

特点:

  1. 不走 JWT
  2. 对应 refresh_session
  3. 不通过 SpacetimeDB 透传

结论:

  1. 只有 access token 需要本设计文档中的 claims
  2. refresh cookie 不追加 JWT claims 设计复杂度

5. Claims 总体设计

当前阶段固定采用:

5.1 标准字段

字段 必填 说明
iss 发行者,固定为 Axum 鉴权发行者。
sub 稳定用户 ID对应 user_account.user_id
aud 当前阶段可暂不强制;若启用,固定为 genarrative-api
iat 签发时间。
exp 过期时间。
nbf 当前阶段不强制。
jti 当前阶段不强制;若后续需要细粒度吊销,再单独扩展。

5.2 扩展字段

字段 必填 说明
sid 会话 ID对应 refresh_session.session_id 或当前 access token 所属会话。
provider 登录来源,固定为 passwordphonewechat
roles 角色列表;当前默认至少包含 user
ver 用户 token 版本,对应 user_account.token_version
phone_verified 是否已完成手机号验证。
binding_status 账号绑定状态,固定为 activepending_bind_phone
display_name 当前展示名快照,用于少量上游日志/观测;不是授权依据。

6. 关键字段定义

6.1 iss

固定要求:

  1. 必填
  2. 必须可稳定配置
  3. 必须作为 Axum 与 SpacetimeDB 共同信任的发行者标识

当前阶段建议:

  1. 本地开发默认:https://auth.genarrative.local
  2. 测试/生产环境由 GENARRATIVE_JWT_ISSUER 或等价配置显式提供

说明:

  1. 不继续沿用 Node 当前的 genarrative-server-node 这种非 URL 风格字符串。
  2. 既然目标是 OIDC 风格 claimsiss 应该升级为稳定 issuer 标识。

6.2 sub

固定要求:

  1. 必填
  2. 值为稳定用户 ID
  3. 不能使用手机号、微信 openid 或用户名作为 sub

来源:

  1. user_account.user_id

6.3 sid

固定要求:

  1. 必填
  2. 表示当前 access token 所属的会话 ID
  3. 用于会话吊销、全端登出和会话列表关联

来源:

  1. refresh_session.session_id

说明:

  1. 即便当前 access token 是由 refresh cookie 刷新得到,sid 仍固定指向同一会话 ledger。
  2. sid 是会话真相的索引,不是一次 access token 的唯一 ID。

6.4 provider

固定枚举:

  1. password
  2. phone
  3. wechat

来源:

  1. 优先来源于当前会话完成登录时的主 provider
  2. auth_identity.provideruser_account.login_provider 保持兼容

6.5 roles

固定要求:

  1. 必填
  2. 类型为字符串数组
  3. 当前阶段至少包含 user

当前阶段角色基线:

  1. user

说明:

  1. 当前阶段不预支 admin/moderator/devops 等角色体系
  2. 但字段必须现在就冻结,避免后续 breaking change

6.6 ver

固定要求:

  1. 必填
  2. 表示当前用户 token 版本
  3. 用于全局登录失效控制

来源:

  1. user_account.token_version

兼容说明:

  1. 继续兼容当前 Node ver 设计
  2. Axum 校验时必须比对数据库中的最新 token_version

6.7 phone_verified

固定要求:

  1. 必填
  2. true 表示账号已具备已验证手机号
  3. false 表示例如微信待绑手机号场景

来源:

  1. user_account.phone_verified_at != null

6.8 binding_status

固定枚举:

  1. active
  2. pending_bind_phone

来源:

  1. user_account.account_status 的鉴权视图

说明:

  1. 它不是完整账号状态枚举,只是鉴权流程需要的最小绑定状态快照
  2. 禁止把大量内部状态枚举直接透传成 JWT claim

6.9 display_name

固定规则:

  1. 可选
  2. 仅作为展示快照
  3. 不能用于授权或数据归属判断

说明:

  1. 即使写入,也必须把它视为弱一致字段
  2. 后续若变更显示名,不要求立即使所有 JWT 失效

7. 不进入 JWT 的字段

以下内容当前阶段禁止进入 access token

  1. 原始手机号
  2. 手机号脱敏值
  3. 微信 openid / unionid
  4. refresh token hash
  5. 风控状态、captcha 状态、封禁剩余时间
  6. 完整用户资料对象
  7. 审计日志、设备列表、IP、UA

原因:

  1. 这些字段要么敏感,要么高频变动,要么不适合做 claims
  2. 统一由数据库真相或接口读取承担

8. 推荐 payload 形态

{
  "iss": "https://auth.genarrative.local",
  "sub": "usr_123",
  "sid": "sess_456",
  "provider": "wechat",
  "roles": ["user"],
  "ver": 3,
  "phone_verified": false,
  "binding_status": "pending_bind_phone",
  "display_name": "微信旅人",
  "iat": 1713657600,
  "exp": 1713664800
}

说明:

  1. 示例只表达字段形态,不锁死具体编码库细节
  2. iat/exp 由签发库按标准时间戳表达

9. Axum / platform-auth / module-auth / SpacetimeDB 的使用边界

9.1 platform-auth

负责:

  1. 签发 access token
  2. 校验 iss/exp/sub/sid/provider/roles/ver
  3. 解析 claims 为统一 Rust 结构

9.2 module-auth

负责:

  1. 提供 claims 所依赖的用户、会话、绑定状态真相
  2. 定义 providerbinding_statustoken_version 的领域语义

9.3 api-server

负责:

  1. 从 Bearer token 中提取 claims
  2. 做路由级鉴权与用户状态校验
  3. 把最小身份上下文透传给 SpacetimeDB client

9.4 SpacetimeDB

负责:

  1. 基于受信任 issuer 识别主体身份
  2. 在 reducer / view 上下文中读取稳定主体身份

当前阶段约束:

  1. SpacetimeDB 身份判定以 iss + sub 为核心
  2. sid/provider/roles 主要服务于应用层与后续模块授权,不要求第一版在 reducer 中过度使用

10. 与当前 Node token 的映射关系

当前 Node 新 claims 迁移规则
sub = userId sub 原样保留,但语义冻结为稳定用户 ID。
iss = jwtIssuer iss 继续保留,但升级为 OIDC 风格 issuer 标识。
ver = tokenVersion ver 原样保留。
sid 新增,绑定会话主键。
provider 新增,绑定本次登录 provider。
roles 新增,当前至少固定为 ["user"]
phone_verified 新增,表达手机号验证状态。
binding_status 新增,表达待绑手机/已激活状态。
display_name 可选新增。

11. Express / Axum 请求上下文映射

当前 Node Express.Request.auth 只有:

  1. userId
  2. tokenVersion

Rust 侧建议升级为统一 claims 结构,例如:

  1. user_id
  2. session_id
  3. provider
  4. roles
  5. token_version
  6. phone_verified
  7. binding_status

说明:

  1. api-server 不再只传 userId
  2. 但 handler 仍应优先依赖最小必要字段,避免所有路由都耦合完整 claims

12. 不允许的设计漂移

后续实现时禁止出现以下情况:

  1. 继续只保留 sub + ver,却声称已完成 SpacetimeDB 身份透传设计
  2. 把手机号、openid、unionid 等敏感信息直接塞进 JWT
  3. sid 设计成一次 access token 的随机 ID而不是会话 ID
  4. roles 省略,等未来再补,导致后续 claims 结构 breaking change
  5. SpacetimeDB 场景里信任客户端传入的 user id而不是受信任 JWT 的 sub

13. 本任务完成定义

当以下条件满足时JWT claims 设计任务视为完成:

  1. iss/sub/sid/provider/roles 已明确冻结
  2. access token 与 refresh session 的职责边界已切开
  3. Axum、platform-authmodule-authSpacetimeDB 的使用边界已明确
  4. 后续可以直接按这份文档实现签发、校验与身份透传

14. 依据文件

  1. server-node/src/auth/token.ts
  2. server-node/src/middleware/auth.ts
  3. server-node/src/auth/refreshSessionCookie.ts
  4. server-node/src/config.ts
  5. server-node/src/types/express.d.ts
  6. packages/shared/src/contracts/auth.ts
  7. docs/technical/SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md
  8. docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md
  9. docs/technical/SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md