# ADR-012:Pending Session 與 Logged-in Session 共用同一個 Cookie ## 狀態 Accepted — 2026-04-21 ## 上位文件 - [oidc-tdd.md §4.5](../oidc-tdd.md#45-handler-範例與-pending-session) - [adr-010-oidc-bff.md](./adr-010-oidc-bff.md) ## 背景 (Context) OIDC Authorization Code + PKCE flow 在 backend 端會產生兩種 server-side session 狀態: 1. **Pending session**(OIDC dance 進行中):`/api/auth/login` 階段建立,存放 PKCE code_verifier、CSRF state、OIDC nonce、return_to。生命週期短(≤ 10 分鐘,從使用者點 Login 到 IdP 完成同意),UserID 為空。 2. **Logged-in session**(已登入):`/api/auth/callback` 完成後,UserID 從 OIDC sub claim 填入,pending 欄位清空。生命週期長(雛形 24h,Phase 1 設計 7d)。 `oidc-tdd.md §4.5` 的原始設計示意了**兩個獨立 cookie**: - `visiona_pending_sid`(短 TTL,10 分鐘) - `visiona_session`(長 TTL,登入後寫入) 雛形實作(OB2 / OB4)為了減少 cookie 數量、簡化 handler 與 store 邏輯,**改採合一**:兩種 session 共用同一個 `visiona_session` cookie + 同一個 `usersession.Store` record,由 `Session.UserID` 是否為空來區分階段。 OB5 review(2026-04-21)將此偏離標為 Major-3「technical debt 而非漏洞」,建議 Phase 1 上線前評估是否還原 TDD 原設計。本 ADR 在 review round 2 後做出最終決定。 ## 決策 (Decision) **保留合一設計**(pending 與 logged-in 共用同一個 `visiona_session` cookie),**不**還原 TDD §4.5 的兩個 cookie 設計。 ### 配套防護機制 合一設計的潛在風險(pending session 被當成 logged-in 用)由以下三道防線阻擋: 1. **AuthMiddleware 強制檢查 `UserID == ""` → 401 `session_not_authenticated`** - `internal/api/middleware.go` AuthMiddleware 在拿到 session 後立刻判斷 UserID 是否為空 - 空 → 401(pending session 訪問 protected endpoint 一律拒絕) - 此檢查不可拿掉、不可放寬 - 對應測試:`TestOIDCMiddleware_Rejects_PendingSession` 2. **Callback 完成時 rotate session ID**(Fix-A1 / ADR-012 配套) - `internal/api/oidc_auth.go` callback handler 在驗 id_token 成功後立即呼叫 `usersession.Manager.RotateSessionID` - 新 session 從一個全新的 random ID 開始,舊 pending session ID 從 store 中刪除 - 防護 session fixation(OWASP ASVS V3.2.1) - 對應測試:`TestOIDCCallback_RotatesSessionID_PreventsFixation`、`TestManager_RotateSessionID_HappyPath` 3. **Pending state 在 callback 完成同一次 UpdateSession 中清空** - OIDCState / OIDCNonce / OIDCCodeVerifier / Extra["return_to"] 在寫入 user info 的同一次 Update 中清掉 - 確保 logged-in session 不殘留 pending 欄位 ## 理由 (Rationale) ### 為什麼維持合一 1. **Cookie 數量少對 frontend 簡單**:frontend 只關心一個 cookie 是否存在,不需要區分 pending/logged-in 兩個 cookie 的狀態 2. **Handler 邏輯簡單**:`oidcCallbackHandler` 不需要「讀 pending cookie → 建 new logged-in cookie → 清 pending cookie」三步操作;改 RotateSessionID 一步搞定 3. **Store 操作減少**:每個 cookie 對應一個 store record,兩個 cookie 等於 store 寫入次數加倍 4. **防護機制已到位**:上述三道防線(middleware UserID 檢查 + RotateSessionID + 同一次清理)涵蓋原本兩個 cookie 設計要解的問題 ### 為什麼不拆兩個 cookie 1. **大改動**:要動 `usersession.Manager`、`oidc_auth.go` 兩處核心 handler、middleware、所有相關測試 2. **不解新風險**:pending vs logged-in 的混淆風險已由現有防線阻擋;拆兩個 cookie 只是把同一個保護換個位置實作 3. **Phase 1 Redis 化更複雜**:兩個 cookie 對應兩個 store key,Redis 命名空間 / TTL 管理變複雜;合一設計直接共用一個 key 4. **TDD §4.5 是文件示意**:原設計是教學性的「直覺易懂」呈現,不是經過威脅模型分析的最佳化方案 ## 取捨 (Trade-offs) ### 優點 - **Cookie 數量少**:browser cookie jar 簡潔,DevTools 偵錯清楚 - **邏輯路徑短**:handler / middleware / store 都只處理一種 cookie + 一種 store record - **Phase 1 換 Redis 影響面小**:同一個 Redis key 即可,不需要兩套 namespace - **測試簡單**:不需要在每個 test 中模擬「兩個 cookie 同時存在 / 其一缺失」的所有組合 ### 缺點 - **理論上 pending 與 logged-in 共用 store record**,若 middleware UserID 檢查被誤刪會立刻變漏洞 - **緩解**:middleware 該行加註解明確警告不可拿掉;CI 可加 grep 檢查 - **`Session` struct 同時有 OIDC pending 欄位 + user info 欄位**,序列化時負擔略大(Phase 1 Redis 化時感受得到) - **緩解**:Phase 1 評估是否分兩個 struct,但仍共用同一個 Redis key + 不同的序列化 schema - **與 oidc-tdd.md §4.5 文件示意不符** - **緩解**:TDD §4.5 已加註「實際採合一設計,詳見 ADR-012」(後續更新) ## 影響範圍 | 區塊 | 影響 | |------|------| | `internal/usersession/usersession.go` | `Session` struct 同時包含 OIDC pending + user info 欄位(已實作) | | `internal/usersession/manager.go` | 新增 `RotateSessionID`(Fix-A1)做為 fixation 防護 | | `internal/api/oidc_auth.go` | callback 流程:state 比對 → ExchangeCode → VerifyIDToken → **RotateSessionID** → set user info → UpdateSession | | `internal/api/middleware.go` | AuthMiddleware 強制檢查 `UserID == ""` → 401(不可拿掉) | | Phase 1 Redis 化 | pending 與 logged-in 共用同一個 Redis key + 同一個 schema;不影響架構 | ## 關聯 - 推翻:無(補充 ADR-010 的實作細節決策) - 相關:[ADR-010](./adr-010-oidc-bff.md)、[ADR-011](./adr-011-supersede-adr-005.md) - 配套修復:Fix-A1(RotateSessionID)、Fix-A2(本 ADR + middleware 註解) - 相關 review:`.autoflow/05-implementation/review/oidc-G5-OB1-OB6-review.md` Major-3