本文 AI 產出,尚未審核

FastAPI ── Session 與 Cookie 管理

主題:設定與讀取 Cookie


簡介

在 Web 開發中,Cookie 是前端與後端之間最常見的狀態保存機制之一。它可以用來記錄使用者的登入資訊、偏好設定、或是臨時的會話資料。對於使用 FastAPI 建置 API 服務時,正確地 設定讀取 以及 保護 Cookie,是提升使用者體驗與安全性的關鍵。

本篇文章將從 概念實作範例常見陷阱 以及 最佳實踐 四個面向,深入說明如何在 FastAPI 中操作 Cookie,讓你能在實務專案中快速上手,從初學者一路晉升為中級開發者。


核心概念

1. Cookie 的基本結構

Cookie 本質上是一個鍵值對,由伺服器在回應 (Response) 中透過 Set-Cookie 標頭送出,瀏覽器隨後在每次請求 (Request) 時自動帶回。常見屬性包括:

屬性 說明
Expires / Max-Age 設定 Cookie 的有效期限
Domain 限定 Cookie 可被哪些子網域使用
Path 限定 Cookie 在哪個路徑下會被送回
Secure 只允許在 HTTPS 連線中傳送
HttpOnly 前端 JavaScript 無法存取,防止 XSS 攻擊
SameSite 防止 CSRF,值可為 LaxStrictNone

2. FastAPI 中的 Cookie 操作介面

FastAPI 內建兩個與 Cookie 互動的物件:

  • Response(或其子類別 JSONResponseHTMLResponse)提供 set_cookie() 方法。
  • Request 提供 cookies 屬性,可直接以字典方式取值。

小技巧:若想在路由函式內同時使用 RequestResponse,只需要在參數中同時聲明兩者,FastAPI 會自動注入實例。

3. 為什麼要簽名 (signed) Cookie?

直接把敏感資訊(如使用者 ID、權杖)寫入 Cookie 會有 資訊被竄改 的風險。使用 簽名(或加密)後,即使使用者可以看到 Cookie 的內容,也無法自行修改而不被伺服器偵測。FastAPI 常搭配 itsdangerous 套件完成簽名與驗證。


程式碼範例

以下示範 4 個實用範例,涵蓋 設定讀取簽名、以及 依賴注入 的寫法。每段程式碼都加上說明註解,方便直接複製到專案中使用。

範例 1:最簡單的 Set‑Cookie

# main.py
from fastapi import FastAPI, Response

app = FastAPI()

@app.get("/set-cookie")
def set_cookie(response: Response):
    """
    直接在回應中設定一個名為 `visited` 的 Cookie,值為 `yes`。
    預設為 Session Cookie(瀏覽器關閉即失效)。
    """
    response.set_cookie(key="visited", value="yes")
    return {"message": "Cookie 已設定"}

重點set_cookiekey 為 Cookie 名稱,value 為字串值。若未指定 max_ageexpires,則為 Session Cookie。


範例 2:讀取 Cookie

# main.py
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.get("/read-cookie")
def read_cookie(request: Request):
    """
    從 Request 物件的 `cookies` 取出名為 `visited` 的 Cookie。
    若不存在則回傳 400 錯誤。
    """
    visited = request.cookies.get("visited")
    if not visited:
        raise HTTPException(status_code=400, detail="Cookie not found")
    return {"visited": visited}

提示request.cookies 回傳的是 MutableMapping,可直接使用字典操作。


範例 3:設定帶有安全屬性的 Cookie

# main.py
from fastapi import FastAPI, Response

app = FastAPI()

@app.get("/set-secure-cookie")
def set_secure_cookie(response: Response):
    """
    設定一個只能在 HTTPS 中傳送,且無法被 JavaScript 存取的 Cookie。
    同時設定 SameSite 為 Lax,降低 CSRF 風險。
    """
    response.set_cookie(
        key="session_id",
        value="random_generated_token",
        max_age=60 * 60 * 24,          # 1 天
        httponly=True,                # 前端無法透過 document.cookie 讀取
        secure=True,                  # 必須在 https 才會傳送
        samesite="lax",               # 防止跨站請求偽造
    )
    return {"message": "Secure cookie set"}

最佳實踐:對於 認證相關 的 Cookie,務必同時開啟 httponlysecuresamesite


範例 4:使用 itsdangerous 簽名 Cookie(防止竄改)

# main.py
from fastapi import FastAPI, Response, Request, HTTPException
from itsdangerous import URLSafeSerializer, BadSignature

app = FastAPI()
# 建議把 secret_key 放在環境變數或設定檔中
serializer = URLSafeSerializer("超級機密的密鑰")

@app.get("/login")
def login(response: Response, user_id: int):
    """
    登入成功後,將使用者 ID 以簽名方式寫入 Cookie。
    前端只能拿到簽名後的字串,無法自行改動。
    """
    signed_user_id = serializer.dumps(user_id)
    response.set_cookie(
        key="auth_token",
        value=signed_user_id,
        max_age=3600,    # 1 小時
        httponly=True,
        secure=True,
        samesite="strict",
    )
    return {"message": "Logged in"}

@app.get("/profile")
def profile(request: Request):
    """
    讀取並驗證簽名的 Cookie,若驗證失敗則拋出 401。
    """
    token = request.cookies.get("auth_token")
    if not token:
        raise HTTPException(status_code=401, detail="未提供 auth_token")
    try:
        user_id = serializer.loads(token)
    except BadSignature:
        raise HTTPException(status_code=401, detail="Cookie 簽名驗證失敗")
    return {"user_id": user_id, "profile": "這裡放使用者資料"}

說明URLSafeSerializer 會產生類似 WyIxMjMiXQ== 的字串,既可安全放入 Cookie,又能在伺服器端解碼驗證。


範例 5:利用 Depends 抽象出 Cookie 讀取邏輯

# main.py
from fastapi import FastAPI, Depends, Request, HTTPException

app = FastAPI()

def get_current_user(request: Request):
    """
    依賴函式:統一從 Cookie 取得使用者 ID,若失敗則拋出例外。
    這樣在每個需要驗證的路由上,只要加入 `current_user: int = Depends(get_current_user)`
    即可。
    """
    token = request.cookies.get("auth_token")
    if not token:
        raise HTTPException(status_code=401, detail="未登入")
    # 這裡直接假設 token 為 user_id,實務上仍建議使用簽名驗證
    try:
        user_id = int(token)
    except ValueError:
        raise HTTPException(status_code=401, detail="Invalid token")
    return user_id

@app.get("/dashboard")
def dashboard(current_user: int = Depends(get_current_user)):
    return {"msg": f"歡迎使用者 {current_user} 進入儀表板"}

好處:把驗證邏輯抽離成依賴,可讓路由保持乾淨,且容易在未來加入簽名或 JWT 驗證。


常見陷阱與最佳實踐

陷阱 可能的後果 建議的解決方案
忘記設定 HttpOnly 前端 JavaScript 可讀取 Cookie,增加 XSS 竊取風險。 務必 為認證或會話類 Cookie 加上 httponly=True
在 HTTP 環境下使用 Secure Cookie 不會被瀏覽器送出,導致登入失效。 僅在正式部署的 HTTPS 環境開啟 secure=True,開發時可暫時關閉。
SameSite 設為 None 卻未加 Secure 現代瀏覽器會直接拋棄 Cookie。 若需跨站請求,使用 samesite="none" 並同時設定 secure=True
在 Cookie 中存放過大資料 超過 4KB 限制會導致 Cookie 被截斷或丟失。 只存放必要的辨識碼(如 session_id),其餘資料放在伺服器端儲存。
直接寫入明文敏感資訊 可能被竊取或自行篡改。 使用 簽名/加密(itsdangerous、JWE)或改用 JWTRedis Session
未設定有效期限 Cookie 成為 Session Cookie,關閉瀏覽器即失效,使用者體驗受影響。 根據需求設定 max_ageexpires,並配合 refresh 機制。

總結最佳實踐

  1. 最小化 Cookie 內容:只存放唯一識別碼(例如 session_id),所有業務資料放在資料庫或快取。
  2. 啟用安全屬性httponly=Truesecure=True(HTTPS)以及合適的 samesite
  3. 使用簽名或加密:防止 Cookie 被惡意修改。
  4. 統一驗證邏輯:透過 FastAPI 的 Depends 抽象出驗證流程,保持程式碼乾淨。
  5. 妥善設定過期:依需求設定 max_age,並在必要時提供刷新(refresh)端點。

實際應用場景

場景 為何使用 Cookie 相關程式碼片段
使用者登入認證 將簽名後的 user_id 或 JWT 放入 auth_token,每次請求自動攜帶。 範例 4 中的 login / profile
語系或主題偏好 只需保存 lang=zh-TWtheme=dark,讓前端在每次載入時直接套用。 response.set_cookie(key="lang", value="zh-TW", max_age=30*24*3600)
A/B 測試分組 伺服器在首次訪問時分配組別編號,寫入 Cookie,之後保持同一組別。 response.set_cookie(key="ab_group", value="B", max_age=7*24*3600)
CSRF Token 伺服器產生一次性 token,寫入 SameSite=NoneSecure 的 Cookie,前端再從表單或 Header 中讀取驗證。 response.set_cookie(key="csrf_token", value=token, httponly=False, secure=True, samesite="strict")
跨子域共享登入狀態 設定 Domain=.example.com,讓 app.example.comadmin.example.com 共用同一 Cookie。 response.set_cookie(key="session_id", value=..., domain=".example.com")

總結

本文從 Cookie 的基本概念 出發,說明了在 FastAPI 中如何 設定讀取,以及 保護 Cookie 的全套流程。透過 4~5 個實作範例,我們展示了:

  • 基本的 set_cookierequest.cookies 用法
  • 加入 安全屬性HttpOnlySecureSameSite)的最佳做法
  • 使用 itsdangerous 簽名 Cookie,防止被竄改
  • 利用 Depends 抽象驗證邏輯,提升程式碼可維護性

最後,我們列舉了常見的 陷阱最佳實踐,以及 實務場景,希望讀者在開發 API 時,能夠 安全、有效率 地管理 Session 與 Cookie,為使用者提供更好的體驗與更高的安全性。

祝開發順利,快把這些技巧寫進你的 FastAPI 專案吧! 🚀