visionA/docs/autoflow/04-architecture/adr/adr-010-oidc-bff.md
jim800121chen fb7da5d180 chore(autoflow): migrate .autoflow/ 共享層文件至 docs/autoflow/
依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類
共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git),
讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等
per-branch 筆記。

- 02-prd/        21 個檔(PRD、features、market-analysis 等)
- 03-design/     18 個檔(design-spec、wireframes、flows 等)
- 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等)
- 07-delivery/   3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup)

合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv,
但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
2026-05-04 16:55:55 +08:00

9.4 KiB
Raw Blame History

ADR-010OIDC 接入策略 — 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.jsApp Router目前 token 存 localStorage已標為 Phase 1 必還的安全債)
  • visionA-backend 是 GoGin目前無 cookie session 機制
  • Member Center 強制 OAuth client 必須是 confidential要求 client_secret這就排除了 SPA + PKCE
  • Member Center 雛形 OAuth client 註冊機制有 usage 欄位 limitationweb_app 類型,需用 webhook_outbound 暫代)
  • visionA Agentlocal-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.1PKCE 是 Authorization Code 的 RFC 7636 強化)
  • code_challenge_method=S256
  • 三個隨機值PKCE verifier、CSRF state、OIDC nonce由 backend 產生並存 server-side pending session

2. PatternBFFBackend-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 cookieHttpOnly + Secure + SameSite=Lax

3. Identity ProviderInnovedus Member Center

  • 不用 Auth0 / Cognito / Clerk — 集團內部解,跨產品 SSO
  • dev 環境:直接用真 Member Centerdocker-compose 一鍵起 postgres + member-center + visionA-backend + visionA-frontend
  • 不寫 mock OIDC server — 多套程式碼維護,與真實環境差異會藏 bug
  • 雛形:InMemoryStore 在 visionA-backend 進程內持有
  • Cookie 內容是 <session_id>.<HMAC-SHA256(session_id)>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 subUUID其他 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 rotationMember Center 暫無),但因為 cookie 7 天 TTL + 24h idle使用者不會頻繁重登
  • WS 連線 cookie 自動帶(同 domain不需 querystring

考慮過的替代方案

方案 ASPA + PKCEpublic clientfrontend 持 token

項目 評估
可行性 Member Center 強制 confidential client無法做
優點 backend 簡單;標準 SPA 模式
缺點 Token 在 browser被 XSS 偷的風險高MC 不支援
排除原因 Member Center 不支援 public client硬性排除

方案 BAuth0 / Cognito / Clerk第三方 vendor

項目 評估
優點 現成 UI、社交登入、MFA、密碼重設都做好
缺點 Vendor lock-inMAU 增加成本線性上升;跨 Innovedus 產品 SSO 需要他們的企業方案;資料外流到第三方
排除原因 與「跨 Innovedus 產品線統一 SSO」目標衝突長期成本與資料治理風險

方案 C自刻 OAuth + JWT

項目 評估
優點 完全掌控
缺點 要自己做密碼重設、email 驗證、2FA、暴力破解防禦維運成本高安全風險自承
排除原因 重複造輪子Member Center 已存在且專為此而生

方案 D繼續用 StaticAuthProvider

項目 評估
優點 不用做事
缺點 無法多用戶測試;無法進 Phase 1
排除原因 Phase 0.6 的目的就是要升級

方案 EImplicit FlowOAuth 2.0 舊版)

項目 評估
優點 簡單,無需 token endpoint
缺點 OAuth 2.1 已 deprecatedtoken 直接在 URL fragment安全性差
排除原因 業界共識:不要用 Implicit Flow

方案 FBFF 但 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登入按鈕變成一個 <a href>
  • 與業界對齊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 程式碼

合規性

  • Architect 確認
  • 使用者裁決 Q1接入路線 = BOAuth Redirect + Authorization Code + PKCE
  • 使用者裁決 Q4完全取代 StaticAuth不保留 dev fallback
  • 使用者裁決 Q5Agent 不動Pairing 流程不變)
  • 使用者裁決 Q6BFF Pattern
  • 使用者裁決 Q7dev 直接用真 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.mdAuth 部分被本 ADR 推翻DB 部分仍有效)
  • 同層:adr/adr-011-supersede-static-auth.mdOB6 任務時建立;明確記錄推翻 ADR-005 Auth 部分)
  • 詳細實作:oidc-tdd.mdPhase 0.6 OIDC TDD 增補)
  • 安全:security.md §2、§14前端安全債清單
  • 既有 Auth 設計:TDD.md §2.2AuthService + AuthProvider 雙層 interface

版本記錄

日期 版本 變更
2026-04-26 1.0 初版 — 反映 Phase 0.6 七個議題的使用者裁決