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

206 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `/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_at``last_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 成功响应
```json
{
"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](./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 > now``revoked_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. 文档、任务清单与测试已同步更新。