# ADR-010:OIDC 接入策略 — Authorization Code + PKCE + BFF Pattern + Member Center ## 狀態 Accepted — 2026-04-26 ## 背景 (Context) Phase 0 雛形採 `StaticAuthProvider`(任何帳密都通過、永遠回 `demo-user`)— 由 ADR-005 規範。雛形已交付(Phase 0 + Phase 0.5 全綠),現在進到 Phase 0.6: 1. **真實使用者是 Phase 1 的前置條件** — 沒有真 user 就無法做多用戶測試、無法上線給內部 FAE 試用 2. **同期 Innovedus Member Center 已可用** — C# .NET Core + OpenIddict + PostgreSQL,已實作 OAuth Authorization Code + PKCE + JWKS + OpenID Connect Discovery 3. **跨產品 SSO 是 Innovedus 集團方向** — visionA、kneron_model_converter、未來其他產品線共用一套帳號 Phase 0.6 必須決定: - **接什麼 Auth?** 自刻 / 第三方 vendor / Member Center - **OAuth flow 怎麼跑?** SPA + PKCE / BFF / Implicit Flow - **frontend 怎麼處理 token?** localStorage / cookie / server-side session - **dev 環境怎麼起?** mock OIDC / 真 Member Center ### 約束條件 - visionA-frontend 是 Next.js(App Router),目前 token 存 localStorage(已標為 Phase 1 必還的安全債) - visionA-backend 是 Go(Gin),目前無 cookie session 機制 - Member Center 強制 OAuth client 必須是 confidential(要求 client_secret)— **這就排除了 SPA + PKCE** - Member Center 雛形 OAuth client 註冊機制有 `usage` 欄位 limitation(無 `web_app` 類型,需用 `webhook_outbound` 暫代) - visionA Agent(local-agent)的 Pairing Token 流程已實作且驗證 OK,**不應動到** - 雛形階段使用者是內部開發者 + 內部 FAE,可接受重啟即重登 ## 決策 (Decision) 採 **OAuth 2.0 Authorization Code Grant + PKCE + BFF Pattern + Innovedus Member Center**。 ### 具體做法 #### 1. 流程:Authorization Code + PKCE redirect - 標準 OAuth 2.1(PKCE 是 Authorization Code 的 RFC 7636 強化) - `code_challenge_method=S256` - 三個隨機值(PKCE verifier、CSRF state、OIDC nonce)由 backend 產生並存 server-side pending session #### 2. Pattern:BFF(Backend-for-Frontend) - visionA-backend 是 OIDC **confidential client**(持有 client_secret) - visionA-backend 處理完整 OIDC dance:產 PKCE → 302 to MC → 接 callback → 換 token → 驗 id_token → 建 cookie session - visionA-frontend **完全不接觸** access_token / id_token / refresh_token - visionA-frontend 只看到一個 `visiona_session` cookie(HttpOnly + Secure + SameSite=Lax) #### 3. Identity Provider:Innovedus Member Center - 不用 Auth0 / Cognito / Clerk — 集團內部解,跨產品 SSO - dev 環境:直接用真 Member Center(docker-compose 一鍵起 postgres + member-center + visionA-backend + visionA-frontend) - 不寫 mock OIDC server — 多套程式碼維護,與真實環境差異會藏 bug #### 4. Session 管理:In-memory + Cookie - 雛形:`InMemoryStore` 在 visionA-backend 進程內持有 - Cookie 內容是 `.`,server side 才能對應到 user / token - 重啟即消失(雛形 Phase 0.6 可接受,內部測試者) - Phase 1 換 Redis / DB(接同 interface) #### 5. 完全取代 StaticAuthProvider - `internal/auth/static.go` + `static_provider.go` 移除 - 不保留 dev fallback(不做 `if env=dev then StaticAuth else OIDC` 切換) - 開發者要登入就要起 Member Center,避免 dev / staging / prod 行為分歧 - env var `VISIONA_AUTH_MODE=oidc` 預設;`static` 仍保留以備未來需要 #### 6. 不動 Pairing Token / Agent - 既有 Pairing Token + Session Token + Agent 流程完全不動 - 改 OIDC 後 `UserContext.UserID` 從 `"demo-user"` 變成 OIDC `sub`(UUID),其他 handler 邏輯不變 - Agent 端不知道 user 是誰、不接 OIDC #### 7. 順便清三個前端安全債 `security.md` §14.1 / §14.2 / §14.3 標記的三個雛形安全債(localStorage token、無 refresh、WS querystring token)— 改 OIDC 同時解掉: - Token 在 backend,不存 localStorage - 雛形 Phase 0.6 不做 refresh token rotation(Member Center 暫無),但因為 cookie 7 天 TTL + 24h idle,使用者不會頻繁重登 - WS 連線 cookie 自動帶(同 domain),不需 querystring ## 考慮過的替代方案 ### 方案 A:SPA + PKCE(public client,frontend 持 token) | 項目 | 評估 | |------|------| | 可行性 | ❌ Member Center 強制 confidential client,無法做 | | 優點 | backend 簡單;標準 SPA 模式 | | 缺點 | Token 在 browser,被 XSS 偷的風險高;MC 不支援 | | 排除原因 | **Member Center 不支援 public client,硬性排除** | ### 方案 B:Auth0 / Cognito / Clerk(第三方 vendor) | 項目 | 評估 | |------|------| | 優點 | 現成 UI、社交登入、MFA、密碼重設都做好 | | 缺點 | Vendor lock-in;MAU 增加成本線性上升;跨 Innovedus 產品 SSO 需要他們的企業方案;資料外流到第三方 | | 排除原因 | **與「跨 Innovedus 產品線統一 SSO」目標衝突;長期成本與資料治理風險** | ### 方案 C:自刻 OAuth + JWT | 項目 | 評估 | |------|------| | 優點 | 完全掌控 | | 缺點 | 要自己做密碼重設、email 驗證、2FA、暴力破解防禦;維運成本高;安全風險自承 | | 排除原因 | **重複造輪子;Member Center 已存在且專為此而生** | ### 方案 D:繼續用 StaticAuthProvider | 項目 | 評估 | |------|------| | 優點 | 不用做事 | | 缺點 | 無法多用戶測試;無法進 Phase 1 | | 排除原因 | **Phase 0.6 的目的就是要升級** | ### 方案 E:Implicit Flow(OAuth 2.0 舊版) | 項目 | 評估 | |------|------| | 優點 | 簡單,無需 token endpoint | | 缺點 | OAuth 2.1 已 deprecated;token 直接在 URL fragment,安全性差 | | 排除原因 | **業界共識:不要用 Implicit Flow** | ### 方案 F:BFF 但 IdP 用 Keycloak / Ory Kratos 自架 | 項目 | 評估 | |------|------| | 優點 | 開源、標準、可控 | | 缺點 | 多一套服務要維運;與 Member Center 重複定位 | | 排除原因 | **Innovedus 已選定 Member Center,沒理由再起一套** | ## 後果 (Consequences) ### 正面影響 - **跨 Innovedus 產品 SSO**:使用者一組帳號用所有 Innovedus 產品 - **安全性提升**:BFF 把 token 守在 backend,順便清三個前端安全債(§14.1 / §14.2 / §14.3) - **frontend 簡化**:不用做 PKCE、不用管 token 刷新、不用處理 callback;登入按鈕變成一個 `` - **與業界對齊**:BFF + Authorization Code + PKCE 是 OAuth 2.1 推薦做法 - **dev 環境真實**:直接接真 Member Center,無 mock 與 prod 行為差異 - **Agent 流程零影響**:Pairing / tunnel 完全不動 ### 負面影響(接受的取捨) - **Backend 多一塊責任**:cookie session store + OIDC client + JWKS cache(已寫進 `internal/oidc/` + `internal/usersession/`) - **dev 必須起 Member Center**:開發者第一次 setup 多一步(`docker compose up`);用 Makefile 一鍵化緩解 - **重啟即消失**:雛形 in-memory session 重啟 → 全使用者重登(Phase 0.6 階段內部測試可接受;Phase 1 上 Redis) - **依賴 Member Center 上線**:MC 掛掉 visionA 也無法登入(Phase 1 需考慮 MC 的 SLO + circuit breaker) - **Member Center `usage` limitation**:雛形暫用 `usage=webhook_outbound`,命名語意不對;需在 MC 開 issue 加 `usage=web_app` ### 風險 | 風險 | 緩解 | |------|------| | Member Center dev 環境不穩 → 影響 visionA 開發 | docker-compose 固定版本;Member Center 團隊維持基礎可用性 | | Member Center 改 schema / API → visionA 跟著爆 | 用 OIDC 標準介面(discovery + JWKS)— Member Center 改實作不改介面就 OK | | Cookie SameSite 在某些瀏覽器舊版有兼容問題 | 雛形支援的瀏覽器是現代版 Chrome/Firefox/Safari/Edge,無此問題 | | OIDC client_secret 外洩 | env / Secrets Manager + 不 commit + log 不印 secret + 可隨時 rotate | | Member Center webhook(用戶刪除 / 停用通知)未實作 | 雛形不接收;Phase 1 補(記入 TODO) | | `usage=webhook_outbound` 之後 MC 真的有 webhook 場景時撞名 | 開 issue 推 MC 加 `usage=web_app`;切換無需動 visionA 程式碼 | ## 合規性 - [x] Architect 確認 - [x] 使用者裁決 Q1(接入路線 = B:OAuth Redirect + Authorization Code + PKCE) - [x] 使用者裁決 Q4(完全取代 StaticAuth,不保留 dev fallback) - [x] 使用者裁決 Q5(Agent 不動,Pairing 流程不變) - [x] 使用者裁決 Q6(BFF Pattern) - [x] 使用者裁決 Q7(dev 直接用真 Member Center + docker-compose) - [ ] OB6 任務:寫 ADR-011 + 更新 ADR-005(標 Auth 部分被推翻) - [ ] OB4 完成時做一次 demo 給使用者看 login flow - [ ] 在 Member Center 專案開 issue:請新增 `usage=web_app` OAuth client 類型 ## 相關文件 - 上位:`adr/adr-005-no-db-auth-in-prototype.md`(Auth 部分被本 ADR 推翻;DB 部分仍有效) - 同層:`adr/adr-011-supersede-static-auth.md`(OB6 任務時建立;明確記錄推翻 ADR-005 Auth 部分) - 詳細實作:`oidc-tdd.md`(Phase 0.6 OIDC TDD 增補) - 安全:`security.md` §2、§14(前端安全債清單) - 既有 Auth 設計:`TDD.md` §2.2(AuthService + AuthProvider 雙層 interface) ## 版本記錄 | 日期 | 版本 | 變更 | |------|------|------| | 2026-04-26 | 1.0 | 初版 — 反映 Phase 0.6 七個議題的使用者裁決 |