YouMind
登入

透過 BFF 架構將 Token 移出瀏覽器

@farstep_
日語2026年6月01日
295K
604
38
0
1.1K

TL;DR

本文說明 BFF 架構如何透過在伺服器端處理 OAuth token 並使用 HttpOnly cookie 來保護 SPA 應用程式,進而大幅降低 XSS 漏洞帶來的影響。

當在單頁應用程式(SPA)中處理 OAuth 時,存取權杖與更新權杖該存放在何處,一直是長期的爭論。localStorage、sessionStorage 以及記憶體變數都無法完全抵禦 XSS。Backend for Frontend(BFF)模式是一種將權杖保留在伺服器端、而非傳遞給瀏覽器的設計。本文將整理其機制與關鍵實作要點。

前置條件

本文假設以下環境:

  • 使用 OAuth 2.0 與 OpenID Connect 開發瀏覽器型應用程式。
  • 存在一個 SPA、它所呼叫的 API,以及一個授權伺服器。
  • SPA 與其支援的伺服器端元件可置於相同父網域下。
  • HTTPS 為前提條件(用於發行 Secure Cookie)。

瀏覽器型應用程式中的 XSS 風險

在瀏覽器中執行的應用程式程式碼,容易受到瀏覽器執行環境中一切行為的影響。XSS 的影響範圍廣泛,且由於攻擊程式碼在與應用程式相同的環境中執行,因此可能進行以下操作:

  • 讀取儲存在 localStorage 或 sessionStorage 中的值。
  • 讀取可從 JavaScript 存取的記憶體變數。
  • 執行應用程式所能進行的所有 API 呼叫。
  • 透過覆寫內建函式(原型污染)來改變行為。

XSS 有多種進入點,例如依賴函式庫的漏洞、自身程式碼輸入/輸出處理的缺陷,或第三方指令碼的入侵。由於完全防止 XSS 相當困難,務實的策略是「在發生入侵時限制其影響」。

只要權杖放在瀏覽器中,權杖經由 XSS 被竊取的可能性就始終存在。若攻擊者在自己的環境中使用竊取到的更新權杖,即使用戶關閉網站,他們也能長時間呼叫 API。雖然權杖輪換與閒置逾時可以減緩影響,但並非根本解決方案。

不將權杖放在瀏覽器中的設計

BFF 模式提供一個專屬於 SPA 的伺服器端元件,並將 OAuth 客戶端責任集中於此。SPA 不直接處理 OAuth 流程,而是透過 BFF 進行身分驗證與 API 呼叫。

角色劃分如下:

  • 與授權伺服器的 OAuth 協定通訊:由 BFF 處理。
  • 持有存取權杖與更新權杖:僅限 BFF。
  • 維護 SPA 與 BFF 之間的身分驗證狀態:使用 HttpOnly Cookie。
  • API 呼叫:SPA 將請求送至 BFF,BFF 將 Cookie 轉換為權杖再轉發給 API。

在此配置中,權杖僅在 BFF 與它所呼叫的 API 之間流轉,對瀏覽器的 JavaScript 不可見。即使發生 XSS,攻擊者也無法提取權杖並從其他地方使用。攻擊者所能做的僅是在用戶當前開啟的工作階段範圍內,向 BFF 發送請求。雖然此影響不可忽視,但其持續時間與影響範圍與權杖被竊相比,已大幅受限。

BFF 扮演 OAuth 術語中所謂的「機密客戶端」。它持有客戶端密碼,並完全在伺服器端完成授權碼交換權杖與更新處理。

身分驗證流程

典型的身分驗證流程如下:

farstep on X — cover
  1. SPA 啟動登入時,向 BFF 發送登入請求。
  2. BFF 啟動 PKCE 授權碼流程,產生導向授權伺服器的重新導向 URL,並回傳給 SPA。
  3. SPA 將瀏覽器導向該 URL,用戶在授權伺服器進行身分驗證。
  4. 授權伺服器將授權碼回傳至 BFF 的重新導向 URI。
  5. BFF 用授權碼交換權杖,取得存取權杖與更新權杖。
  6. BFF 將權杖儲存在自己的安全區域(加密 Cookie、伺服器端工作階段儲存空間等),並僅透過 HttpOnly Cookie 將工作階段識別碼回傳給 SPA。
  7. SPA 呼叫 API 時,透過 BFF 發送請求。Cookie 會同時發送,BFF 將其轉換為存取權杖再轉發給上游 API。
  8. 若存取權杖過期,BFF 會使用更新權杖靜默更新。

從 SPA 的角度來看,登入狀態由 Cookie 維護,而 API 請求則以一般 fetch 呼叫完成。SPA 中不存在直接處理 OAuth 權杖或授權碼的程式碼。

必要的安全設定

BFF 發行的 Cookie 必須設定以下屬性:

  • HttpOnly:禁止從 JavaScript 存取。即使有 XSS,也無法讀取 Cookie 內容。
  • Secure:僅透過 HTTPS 發送。
  • SameSite=Strict:確保 Cookie 不會隨其他網站的請求發送。這有助於阻擋主要的 CSRF 攻擊路徑,但 CSRF 防護並非僅靠此項即可完成。
  • __Host- 前綴:在 Cookie 名稱前加上 __Host-,確保瀏覽器將 Cookie 限制在發行主機內,不會與子網域共用。

若將權杖儲存在加密 Cookie(客戶端工作階段)中,則 Cookie 內容會加密。若將權杖放在伺服器端工作階段儲存空間(伺服器端工作階段)中,則 Cookie 僅包含工作階段識別碼,因此不需加密。

CSRF 防護不應僅依賴 SameSite

由於採用 Cookie 基礎的身分驗證,BFF 必須實作 CSRF 防護。SameSite=Strict 是有效的一步,但並非完整解決方案。尤其當 SPA 放置於 www.example.com 而 BFF 放置於 api.example.com 時需特別注意。由於 SameSite 是依網站而非來源判定,來自 example.com 下其他子網域的請求會被視為「相同網站」,即使使用 SameSite=Strict,Cookie 仍會被發送。

因此,請使用以下其中一種方式加強 CSRF 防禦:

  • 若 BFF 與 SPA 位於不同來源,則使用 CORS 與 Origin 標頭驗證進行防禦。
  • 對於改變狀態的請求,使用防偽 / 雙重提交 Cookie 方法驗證 CSRF 權杖。

將 CORS 限制為確切來源

CORS 應僅允許 SPA 的確切來源。對於涉及 Cookie 的認證請求,瀏覽器不允許在 Access-Control-Allow-Origin 中使用萬用字元(*)。因此,BFF 必須在回應中反映 SPA 的確切來源。這種嚴格限制允許來源的做法,可作為 CSRF 防禦的一部分。

BFF 所持有的工作階段資料(尤其是儲存在加密 Cookie 中的權杖資訊)需要金鑰管理。請將安全注入金鑰作為 BFF 設定的一部分,並定期輪換金鑰。

請注意,將 SPA 與 BFF 置於相同父網域,是為了滿足 Same-Site Cookie 作為第一方 Cookie 運作的條件。

演進:API 驅動的 BFF

若 BFF 像傳統 Web 應用程式一樣建置,BFF 的伺服器端渲染會介入 SPA 的頁面切換。API 驅動的 BFF 能最小化此影響。

角色分為兩個:

  • OAuth Agent:負責 OAuth 協定處理的 API。它透過 JSON 從 SPA 呼叫。
  • OAuth Proxy:作為 API 閘道外掛運作,從 Cookie 中提取權杖,再轉發給上游 API。

在此配置中,SPA 僅需將 OAuth Agent 視為一般 REST API 呼叫。SPA 前端開發體驗幾乎可維持與導入 BFF 前相同。

採用時的考量

採用 BFF 模式時,請考慮以下幾點:

  • 增加的架構元件會提高開發與運維成本。
  • SPA 與 BFF 必須置於相同父網域。
  • 僅透過反向代理轉發 Authorization 標頭的配置並非 BFF。BFF 的本質在於作為機密客戶端持有權杖。
  • PKCE 與 BFF 是搭配使用,而非替代關係。
  • BFF 在轉發前必須驗證目標主機,以防止權杖暴露給非預期的主機。
  • 登出設計會變得複雜,因為必須同時連動 SPA 工作階段、BFF Cookie 與授權伺服器工作階段。

總結

在瀏覽器型應用程式中安全處理權杖的實務做法,就是不將權杖放在瀏覽器中。BFF 模式將 OAuth 客戶端責任移至伺服器端,僅將 HttpOnly Cookie 傳遞給瀏覽器。這樣即使發生 XSS,也能防止權杖被竊。透過將角色劃分為 OAuth Agent 與 OAuth Proxy,可在維持 SPA 開發體驗的同時強化安全性。

一鍵儲存

使用 YouMind AI 深度閱讀爆款文章

保存原文、追問細節、總結觀點,並在一個 AI 工作空間裡把爆款文章沉澱成可複用筆記。

了解 YouMind
寫給創作者

把你的 Markdown 變成乾淨的 𝕏 文章

圖片上傳、表格、程式碼區塊,往 𝕏 上手動重排太痛苦。YouMind 把整篇 Markdown 一鍵轉成乾淨、可直接發佈的 𝕏 文章草稿。

試試 Markdown 轉 𝕏

更多可拆解樣本

近期爆款文章

探索更多爆款文章