跨子網域 SSO:用 Supabase SSR Cookie 建立可撤銷的登入邊界
從 Cookie Domain、SameSite、Secure 到 Supabase token refresh,逐步建立跨子網域登入並保留登出、輪替與故障復原能力。
截至 2026-07-29,Supabase 的 SSR 指南仍以「把 session 放進 Cookie、在伺服器端刷新 token」作為 SSR 整合的核心。跨子網域 SSO 並不是把瀏覽器的 localStorage 複製到另一個網站,而是先決定一個可信任的父網域,再讓伺服器以受控 Cookie 建立共同的 session 邊界。這個決定會同時影響登入、登出、CSRF、快取與事故處理。 假設服務位於 www.example.com 、 class.example.com 與 app.example.com 。若三者由同一團隊管理、共用相同 Supabase project,才適合評估父網域 Cookie。若其中任何子網域可由第三方部署或寫入內容,把認證 Cookie 擴大到 .example.com 會增加攻擊面;此時應改用一次性授權碼或中央 identity broker,而不是分享 Cookie。 實作步驟 第一步是畫出信任邊界。逐一列出能控制 DNS、TLS、部署與回應標頭的人,確認所有接收 Cookie 的子網域都屬於同一安全等級。登入回呼 URL 必須列入 Supabase Auth 的允許清單,redirect 參數則只能落在明確 allowlist,不能把使用者提供的任意 URL 直接帶入。 第二步是在每個 SSR 應用建立 Supabase browser client 與 server client。Supabase 官方 SSR 文件說明,SSR 情境應以 Cookie 取代 server 無法讀取的 local storage,並依指南使用 PKCE。Cookie adapter 必須能讀取 request cookie,也能把刷新後的 token 寫回 response。不同框架的 API 會變動,因此下列是邊界示意,不是可直接貼上的 framework adapter: const cookieOptions = { domain: ".example.com", path: "/", secure: true, sameSite: "lax" as const, httpOnly: true, } // Server-only pseudocode: use the current @supabase/ssr adapter for your framework. createServerClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, { cookies: { getAll: () => requestCookies.getAll(), setAll: (updates) => { for (const { name, value, options } of updates) { responseCookies.set(name, value, { ...options, ...cookieOptions }) } }, }, }) Domain=.example.com 讓符合規則的子網域收到 Cookie; Path=/ 讓整個站點可用;正式環境必須搭配 HTTPS 與 Secure 。 HttpOnly 可阻止一般前端 JavaScript 直接讀取 Cookie,但不能取代輸入消毒或 CSP。 SameSite=Lax 常是同站導覽的合理起點;若架構真的需要 cross-site context 才考慮 SameSite=None ,而 MDN 明確指出它必須與 Secure 搭配。不要照抄設定,應用實際 OAuth provider、嵌入場景與 CSRF 防護做測試。 第三步是把 refresh 放在不被共享快取的伺服器路徑。Supabase 的進階指南提醒,帶有 Set-Cookie 的認證回應若被 ISR 或 CDN 共用快取,可能把一位使用者的更新 token 送給另一位使用者。認證 middleware、callback 與個人化頁面應是 dynamic/private response,並設定適當的 Cache-Control 。不要僅解碼 JWT 就當作伺服器授權證明;每個應用仍須取得可信的使用者狀態,資料庫則以 RLS 做最終授權。 第四步是設計完整登出。從任一子網域登出時,除了呼叫 Supabase sign-out,也要用建立時相同的 Domain 與 Path 清除 Cookie。屬性不一致時,瀏覽器可能留下另一顆同名 Cookie,造成「已登出但另一站仍登入」的假象。密碼重設、帳號停權、refresh token 失效與全裝置登出,都要有各自的驗證案例。 最後才處理 CORS。Cookie 是否會被送出與 JavaScript 能否讀取 response 是不同問題。跨 origin API 若需要 credentials,server 必須回傳精確的 Access-Control-Allow-Origin ,不能在 credentials 模式使用萬用 * ;client 也要顯式帶 credentials。即使 CORS 正確,敏感 mutation 仍要驗證 origin/CSRF token,不能把 SameSite 當成唯一防線。 失敗與復原 常見故障一是 Safari/Chrome 只在某一站保持登入。先用 DevTools 查看實際 Set-Cookie ,比對 Domain、Path、Secure、SameSite 與到期時間,而不是先重寫 Auth。若同名 host-only Cookie 與 parent-domain Cookie 同時存在,先在測試環境用精確屬性刪除兩者,再重新登入。 常見故障二是 refresh loop。通常是 server client 寫回 Cookie 失敗、middleware 與 callback 使用不同選項,或快取回放舊 response。復原時先關閉認證路徑快取、保留 request ID、記錄 token 到期時間但不可記錄 token 本身,再比對一次 request/response cookie 變化。 若上線後發現不受信任的子網域也能收到認證 Cookie,應立即停止使用父網域 Domain,清除共享 Cookie、撤銷受影響 session,並改回 host-only Cookie。注意 __Host- 前綴要求不能設定 Domain,因此它適合 host-only 高強度邊界,不能同時拿來做父網域分享。 回滾方案要在發佈前準備:保留舊的單站登入入口,功能旗標可停用共享 Cookie,callback 能導回原站。回滾後要重新測試登入、刷新、登出與 session 撤銷,不能只確認首頁打得開。 驗證指令 在 staging 用獨立測試帳號,從三個子網域逐一執行登入、刷新、登出。以下命令只檢查 response header;不要把真實 token 放入終端輸出或 CI log: curl -sS -D - -o /dev/null https://class.example.com/auth/callback curl -sS -I https://app.example.com/account 驗收時至少確認: 匿名 request 不會收到其他使用者內容,認證 response 也不會被 public cache。 Cookie 只送往預定子網域,正式環境具有 Secure ,敏感 Cookie 為 HttpOnly 。 token 到期後可由 server 正常刷新,而且 response 的 Set-Cookie 不會被共用快取。 任一子網域登出後,其他子網域下一次受保護 request 也失效。 未列入 allowlist 的 redirect 被拒絕,跨 origin mutation 缺少 CSRF/origin 證明時被拒絕。 官方來源 Supabase Server-Side Rendering Supabase SSR Auth advanced guide MDN Set-Cookie header 這些來源說明技術行為,不代表某一組 Cookie 參數對所有架構都安全。實作前仍須依實際框架版本、網域所有權與 threat model review。 延伸閱讀 回到 技術文章 比較其他 Auth 與 Edge 架構。 從 課程總覽 建立完整的前後端安全練習。 若要檢查既有多站登入流程,可查看 顧問服務 。