跨 App SSO
授权码、PKCE、App session 和登出边界
IWish Auth 使用 Portal 作为统一登录入口,业务 App 使用 OAuth 风格授权码和不透明 App session。当前协议不是公开标准 OIDC issuer,业务 App 应通过 SDK 接入,避免依赖 Supabase token 细节。
1. 授权流程
App /auth/login
-> 生成随机 state 和 PKCE verifier/challenge
-> 将 state/verifier 写入 10 分钟 HttpOnly cookie
-> 跳转 Portal authorize URL
Portal
-> 恢复统一登录或要求飞书/邮箱登录
-> 用户选择授权组织
-> POST /v1/sso/authorize
App /auth/callback
-> 校验 state
-> POST /v1/sso/token 交换一次性 code
-> 设置本 App HttpOnly session cookie
业务请求
-> GET /v1/sso/session 解析上下文
2. Authorization Code
- 有效期 60 秒。
- 只能消费一次。
- 与
clientId、精确redirectUri和 PKCE challenge 绑定。 - 数据库使用原子 RPC 消费,避免并发重放。
业务 App 不得记录 code,也不得在浏览器调用 token endpoint。
3. PKCE 和 State
只允许 S256。codeVerifier 和 state 必须是密码学安全随机值,保存在短时 HttpOnly、SameSite=Lax cookie。Callback 必须先校验 state,再交换 code。
缺少 state、state 不匹配、verifier 缺失都必须终止登录,不允许降级继续。
4. App Session
POST /v1/sso/token 返回 opaque accessToken。它不是 JWT,业务 App 不应自行解析。每个受保护请求通过 SDK 调用 GET /v1/sso/session 获取:
session:ID 和过期时间。user:统一身份。employee:飞书员工上下文或null。organization:本次授权域。app:当前 App。roles、permissions:当前 App scoped 权限。clientAssignments:当前有效客户项目分工。
5. 立即失效条件
以下状态变化会使后续 session introspection 失败或不再包含旧权限:
- 用户、组织成员、组织或 App 被停用。
- 飞书员工离职、冻结或不再有效。
- App 角色被撤销。
- OAuth client 被停用或轮换 secret。
- 用户执行全局登出。
- App session 被局部撤销或过期。
业务 App 可以使用最多 5 秒短缓存降低重复 introspection,但不得无限缓存身份和权限。
6. 登出模型
- App 局部登出:
POST /v1/sso/logout,删除本 App cookie。 - Portal 全局登出:
POST /v1/sso/logout-all,撤销全部 App sessions,再退出统一登录。 - 单个 App 不得直接清理其他 App cookie。
7. 稳定错误码
sso_client_not_found、sso_client_disabled、sso_redirect_uri_not_allowed、sso_organization_not_allowed、sso_app_not_allowed、sso_invalid_client、sso_invalid_grant、sso_session_invalid、sso_session_expired、sso_session_revoked。
401 表示登录/session 需要恢复;403 表示用户已登录但没有当前授权,不应循环重试登录。