この記事を読んでできること
サーバーサイド側からSet-Cookie
でクライアントのCookieファイルにトークンを格納できない問題を解決できる
なぜこの記事を書くことにしたか
サーバーサイド側からSet-Cookie
でクライアントにCookieの値を格納しようとしたが、格納されず、解決にかなりの時間を割くことになったため
やりたいこと
サーバーサイド側からSet-Cookie
でクライアントのデベロッパーツールのApplicationのCookieファイルにCSRFトークンの値を格納したい
記述しないこと
- FastAPIの実装について
- Cookieについて
使用技術
バックエンド
- 言語
- Python 3.10.4
- フレームワーク
- Fast API 0.78.0
- データベース
- MySQL 8.0.31
- ライブラリ
- starlette-csrf==1.4.4
※ちなみに
- トークンを発行するシステムはstarlette-csrfというライブラリに任せます
- ライブラリの仕様については最後尾に記載します
- ローカル環境で検証しています
現状
トークンを発行するAPIをフロントエンドに叩いてもらい、NetworkにはレスポンスヘッダにSet-Cookie
が渡されていることが確認できる
しかし、Cookieファイルにはトークンが格納されていない
import os
from fastapi import FastAPI
from starlette_csrf import CSRFMiddleware
from routers import router
app = FastAPI()
app.add_middleware(
CSRFMiddleware,
secret=os.environ["SECRET_KEY"],
)
app.include_router(router.router)
@router.get("/api/get_csrf_token", tags=["token"])
def get_csrf_token():
return "トークンを発行しました。"
Set-Cookieは返ってきているが、cookieファイルにはトークンがない
原因
Cookieの属性について把握をしていなかったため
Response HeaderのSet-Cookieの横にある小さなアテンションマークにカーソルを合わせることで表記される
以下がその要因である
- このSet-Cookieヘッダーによるクッキーの設定は、「SameSite=None」属性は持っているものの、「SameSite=None」を使用するために必要な「Secure」属性を持っていなかったため、ブロックされる
- このSet-Cookieヘッダーによるクッキーの設定の試みは、「Same-Site=Lax」属性を持っているものの、トップレベルナビゲーションに対する応答ではないクロスサイトの応答から来るため、ブロックされる
今回であれば、starlette-csrfライブラリのデフォルトがSame-Site=Lax
のため、2番である
SameSite、Secure属性については以下の記事を参照
SameSite cookies
トップレベルナビゲーションとは
そのリクエストによってアドレスバーに記載されているURLが変化するものであるナビゲーションのこと
以下の記事を参照
SameSite 属性を使った Cookie のセキュアな運用を考える
該当箇所を修正
今回はhttp://localhost:3000
からhttp://127.0.0.1:8000/api/get_csrf_token
へリクエストしており、全く別物のオリジンからくるレスポンスのため、Cookieの属性をNone+Secureにする
starlette-csrfライブラリのREADMEを参照し、以下のように引数を渡す
これによって、CookieにSameSite=None, Secure=True属性を付与することができる
app.add_middleware(
CSRFMiddleware,
secret=os.environ["SECRET_KEY"],
cookie_samesite=None,
cookie_secure=True,
)
Cookieの属性がSameSite=None、Secureであることが確認できる
無事、 Cookieが格納されていることを確認できるstarlette-csrfについての詳細
starlette-csrfの仕様
- 安全なhttpメソッド({“GET”, “HEAD”, “OPTIONS”, “TRACE”})でリクエストされたとき、set-cookieでクライアントのcookieにcsrftokenというkeyで格納する
- クライアントからPOSTリクエストされたとき、x-csrftokenというkeyでcookie headerが送信されることを期待する
- cookieとheaderのtokenの値を比較検証する
- それらが一致した場合、リクエストは処理される
- 一致しない場合は、403 Forbiddenエラー応答が返される
処理の検証結果
- 現状、単純なapi/get_csrf_tokenというエンドポイントを作成し、tokenをセットする
- POSTMANで
- ヘッダにx-csrftokenを用意しない場合は、403エラー
- ヘッダにx-csrftokenを用意したが、valueが一致しない場合も403エラー
- ヘッダにx-csrftokenを用意し、valueが一致した場合、リクエストを処理する