依 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)。
187 lines
9.4 KiB
Markdown
187 lines
9.4 KiB
Markdown
# 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 內容是 `<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 `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;登入按鈕變成一個 `<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 程式碼 |
|
||
|
||
## 合規性
|
||
|
||
- [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 七個議題的使用者裁決 |
|