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

187 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 欄位 limitation`web_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
#### 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 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 程式碼 |
## 合規性
- [x] Architect 確認
- [x] 使用者裁決 Q1接入路線 = BOAuth Redirect + Authorization Code + PKCE
- [x] 使用者裁決 Q4完全取代 StaticAuth不保留 dev fallback
- [x] 使用者裁決 Q5Agent 不動Pairing 流程不變)
- [x] 使用者裁決 Q6BFF Pattern
- [x] 使用者裁決 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.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.2AuthService + AuthProvider 雙層 interface
## 版本記錄
| 日期 | 版本 | 變更 |
|------|------|------|
| 2026-04-26 | 1.0 | 初版 — 反映 Phase 0.6 七個議題的使用者裁決 |