Files
Genarrative/docs/technical/AUTH_REFRESH_ROTATION_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

5.6 KiB
Raw Blame History

/api/auth/refresh 轮换落地设计

日期:2026-04-21

1. 文档目的

这份文档用于指导 M2实现 refresh token 轮换 任务的首版落地,冻结:

  1. POST /api/auth/refresh 的请求与响应 contract。
  2. refresh cookie、服务端 refresh session 与 access token 三者的职责边界。
  3. “会话 ID 稳定、refresh token 可轮换”的固定语义。
  4. Rust 首版在未切入 SpacetimeDB 前的临时进程内实现方式。

2. 当前基线

当前 Node /api/auth/refresh 已具备以下稳定语义:

  1. 从 HttpOnly cookie 中读取原始 refresh token。
  2. 服务端只按 refresh_token_hash 查找当前活跃会话。
  3. refresh 成功后,不新建第二条会话,而是在原会话上轮换 refresh token。
  4. 轮换时会更新 expires_atlast_seen_at
  5. 成功后返回新的 access token并写回新的 refresh cookie。
  6. 失败时会主动清空 refresh cookie要求前端重新登录。

Rust 首版必须保留以上语义。

3. 当前阶段范围

本阶段只落以下内容:

  1. module-auth 增加进程内 refresh session 真相与轮换服务。
  2. api-server 暴露 POST /api/auth/refresh
  3. 登录成功时创建 refresh session。
  4. refresh 成功时在原 session 上轮换 refresh token。
  5. access token 的 sid 固定改为稳定 session_id,不再直接复用 refresh token。

本阶段不包含:

  1. /api/auth/logout
  2. /api/auth/logout-all
  3. /api/auth/sessions
  4. /api/auth/sessions/:sessionId/revoke
  5. SpacetimeDB reducer 真正写表

4. contract

4.1 请求

  1. 方法:POST
  2. 路径:/api/auth/refresh
  3. 请求体:空
  4. 鉴权来源refresh cookie

4.2 成功响应

{
  "token": "<access-token>"
}

同时响应头必须写回新的 refresh cookie。

4.3 失败响应

当 refresh token 缺失、会话不存在、会话已过期或用户不存在时:

  1. 返回 401 UNAUTHORIZED
  2. 同时清理 refresh cookie

5. 固定语义

5.1 session_id 与 refresh token 必须拆开

从本任务开始固定以下规则:

  1. session_id 是稳定会话主键。
  2. refresh token 是可轮换的会话凭证。
  3. access token 的 sid 必须写入 session_id
  4. refresh 轮换只更新 refresh token不更改 session_id

禁止继续把 refresh token 直接塞进 JWT sid

5.2 refresh 是“原会话轮换”

refresh 成功后:

  1. 保留原 session_id
  2. 生成新的原始 refresh token
  3. 用新的 refresh_token_hash 覆盖旧值
  4. 更新 expires_at
  5. 更新 last_seen_at

不允许新建第二条 session。

6. crate 边界

6.1 module-auth

负责:

  1. 管理 refresh session 进程内真相。
  2. 提供创建 refresh session 与轮换 refresh session 的用例。
  3. 提供按 user_id 查询用户快照的能力,供 refresh 成功后重新签发 access token。

不负责:

  1. 生成原始 refresh token。
  2. 读写 cookie。
  3. 签发 JWT。

6.2 platform-auth

负责:

  1. 生成原始 refresh token。
  2. 对 refresh token 做哈希。
  3. 构造 refresh cookie 的 Set-Cookie 头。
  4. 从 cookie header 中读取 refresh token。

6.3 api-server

负责:

  1. 从请求 cookie 中提取 refresh token。
  2. 调用 module-auth 执行 refresh session 轮换。
  3. 根据用户快照与稳定 session_id 重新签发 access token。
  4. refresh 失败时清理 cookie。

7. 进程内存储模型

当前阶段 module-auth 继续使用进程内内存仓储承接 refresh session字段至少包括

  1. session_id
  2. user_id
  3. refresh_token_hash
  4. issued_by_provider
  5. expires_at
  6. created_at
  7. updated_at
  8. last_seen_at
  9. revoked_at

说明:

  1. 这只是 SpacetimeDB 正式落地前的阶段性实现。
  2. 字段命名与语义继续对齐 SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md

8. 流程

8.1 登录创建 session

密码登录成功后:

  1. api-server 生成原始 refresh token。
  2. api-server 计算 refresh_token_hash
  3. module-auth 创建一条新 session并返回稳定 session_id
  4. api-server 用该 session_id 写入 access token 的 sid
  5. api-server 把原始 refresh token 写回 cookie。

8.2 refresh 轮换 session

当请求 POST /api/auth/refresh 时:

  1. 从 cookie 中读取原始 refresh token。
  2. 计算 refresh_token_hash
  3. module-auth 查找当前活跃 session。
  4. 校验 expires_at > nowrevoked_at == null
  5. 读取该 session 对应用户。
  6. 生成新的原始 refresh token。
  7. 用新 hash 更新同一条 session。
  8. 返回新的 access token 与新的 refresh cookie。

9. 错误语义

以下情况统一返回 401

  1. 缺少 refresh cookie
  2. refresh token 命中不到 session
  3. refresh session 已过期
  4. refresh session 已吊销
  5. session 对应用户不存在

错误文案统一保持中文,并沿用“当前登录态已失效,请重新登录”这类恢复导向语义。

10. 测试策略

至少覆盖:

  1. 登录成功后可用 cookie 调用 /api/auth/refresh
  2. refresh 成功会写回新的 cookie
  3. refresh 成功返回新的 access token
  4. refresh 后旧 refresh token 立即失效
  5. 缺少 cookie 时返回 401
  6. 无效 refresh token 时返回 401 且清理 cookie

11. 完成定义

满足以下条件时,本任务视为完成:

  1. Rust 侧已提供 POST /api/auth/refresh
  2. access token sid 已改为稳定 session_id
  3. refresh token 轮换成功时不创建新会话。
  4. refresh 失败时会清理 cookie。
  5. 文档、任务清单与测试已同步更新。