対象読者
この記事は、次のような人を対象にしています。
- CSRFトークンの基本は知っているが、
Sec-Fetch-Siteヘッダーを使った最近のCSRF対策を整理したい人 - Rails 8.2、Laravel 13、Go 1.25、Hono などで導入されている Fetch Metadata ベースのCSRF対策に関心がある人
- 「CSRFトークンはもう不要になるのか?」という疑問を、既存フレームワークの実装例をもとに考えたい人
- 小規模なWebアプリ、管理画面、問い合わせフォーム、ランディングページのフォームAPIを実装・運用する人
- HTTPミドルウェアとしてCSRF対策を自作する場合に、
Sec-Fetch-Site、Origin、CSRFトークンをどう組み合わせるべきか知りたい人 - Baseline 2023 の機能を採用したいが、古いAndroid端末、WebView、アプリ内ブラウザーへのfallbackも気になる人
この記事は、CSRFそのものをゼロから説明する入門記事ではありません。CSRFトークン、Cookie、SameSite属性、Origin、Refererといった用語を聞いたことがある読者を想定しています。
結論
Sec-Fetch-Site ヘッダーは、現代のCSRF対策で積極的に使う価値があります。
Sec-Fetch-Site は、ブラウザーがリクエストの発生元と送信先の関係をサーバーへ伝える Fetch Metadata Request Header です。値には same-origin、same-site、cross-site、none があります。MDNでは Baseline Widely available とされ、主要ブラウザーでは 2023年3月以降に利用可能になっています。
ただし、結論は「CSRFトークンは不要になった」ではありません。
モダンブラウザーでは
Sec-Fetch-SiteをCSRF対策の主信号にできる。
ただし、古いブラウザー、Android WebView、アプリ内ブラウザー、ヘッダー欠落環境を考えるなら、Origin検証やCSRFトークンによるfallbackを残すのが現実的。
CSRFで何が問題になるのか
CSRFは、ログイン済みユーザーのブラウザーに、本人が意図しない状態変更リクエストを送らせる攻撃です。
たとえば、ユーザーが example.com にログイン済みの状態で攻撃者のページを開くと、攻撃者のページから example.com に対して POST リクエストを送らせることができます。ブラウザーは example.com のCookieを自動送信するため、サーバーからは正規ユーザーの操作に見えてしまう可能性があります。
<form action="https://example.com/account/delete" method="POST">
<button>Click</button>
</form>
つまり、CSRFが主に問題になるのは Cookieなどブラウザーが自動送信する認証情報に依存した状態変更リクエスト です。
未ログインの問い合わせフォームでは、典型的なCSRFよりも、スパム、Bot、大量送信、偽問い合わせのほうが主問題になります。Sec-Fetch-Site は入口防御にはなりますが、Bot対策そのものではありません。
Sec-Fetch-Site で何が変わるのか
従来のCSRFトークン方式は、ざっくり言えば次の確認です。
このリクエストは、サーバーが発行した正規のフォームやページから来たはずだ。
一方、Sec-Fetch-Site を使う方式は、次の確認です。
ブラウザー自身が、このリクエストは
same-originなのか、cross-siteなのかを報告している。
代表的な扱いは次のとおりです。
| 値 | 意味 | CSRF対策での扱い |
|---|---|---|
same-origin |
scheme / host / port が同じ | 原則許可候補 |
same-site |
同じsite。サブドメイン違いを含み得る | 設計次第 |
cross-site |
別サイトからのリクエスト | unsafe methodでは原則拒否 |
none |
アドレスバー入力やブックマークなど | 用途ごとに判断 |
OWASPは、Sec-Fetch-Site をCSRF保護の主要な信号として扱い、cross-site の state-changing request を拒否する方針を示しています。また、same-site は兄弟サブドメインを信頼できる場合だけ許可する考え方が示されています。
本番環境ではHTTPS前提で考えます。OWASPも、Fetch Metadata request headers は trustworthy URL に送られるものであり、アプリケーション全体でHTTPSを強制すべきだと説明しています。
Baseline 2023 でも fallback を残したい理由
Sec-Fetch-Site は Baseline 2023 世代の機能として扱いやすくなっています。新規実装で採用する価値は十分にあります。
しかし、Baselineは「主要ブラウザーの現行サポート状況」の目安です。実際のユーザー環境では、古いAndroid端末、Android System WebView、SNSアプリ内ブラウザー、企業端末、更新停止端末が混ざります。
特にAndroidでは、OS、Chrome、WebView、メーカーのセキュリティ更新が分かれます。2026年時点で Chrome for Android のシステム要件は Android 10 以上です。
目安としては、次のように考えるとよいです。
| 環境 | 扱い | 方針 |
|---|---|---|
| Android 9 以下 | レガシー環境 |
Sec-Fetch-Site 前提にしない |
| Android 10〜12 | 移行期 | WebViewやアプリ内ブラウザーに注意 |
| Android 13 以降 | モダン環境扱いしやすい |
Sec-Fetch-Site を主信号にしやすい |
最近の端末では長期サポートも進んでいます。Pixel 8以降は7年間のOS・セキュリティ更新が案内され、Samsungも2024年1月以降、一部Galaxy端末で最大7年のセキュリティ更新サポートを案内しています。
それでも、公開Webアプリでは古い端末やWebViewを無視しにくいため、いきなり Sec-Fetch-Site only にするより、Origin 検証やCSRFトークンのfallbackを残すほうが安全です。
CSRFトークンは不要になるのか
不要になる場合はあります。
たとえば、対象ブラウザーをモダン環境に限定できる社内ツールや管理画面では、Sec-Fetch-Site を主信号にした header-only 型のCSRF対策を採用しやすいです。
しかし、公開Webアプリでは次の理由でCSRFトークンをすぐに捨てるのは慎重にしたほうがよいです。
- 古いブラウザーが
Sec-Fetch-*ヘッダーを送らない場合がある - Android WebViewやアプリ内ブラウザーの挙動にばらつきがある
- HTTPSでない環境では期待どおりに送られない
- プロキシやゲートウェイがヘッダーを削る可能性がある
- 決済、退会、権限変更などは追加確認を残したい
OWASPも、legacy browser では Sec-Fetch-* ヘッダーが送られない場合があるため、Fetch Metadata 実装では標準的な Origin 検証などへのfallbackが必要だと説明しています。欠落時には、リスクと互換性に応じてfail-safeにするか、Origin 検証、CSRFトークン、追加検証へfallbackする方針が示されています。
既存実装から見る設計パターン
最近のフレームワークや標準ライブラリを見ると、主流は「即トークン廃止」ではなく、Sec-Fetch-Site を先に見て、必要ならfallbackする 方向です。
| 実装 | 方針 |
|---|---|
| Rails 8.2 |
header_only と header_or_legacy_token を用意 |
| Laravel 13 |
Sec-Fetch-Site を先に見て、失敗時にCSRFトークンへfallback |
| Go 1.25 |
net/http.CrossOriginProtection でcross-origin checkを実行 |
| Hono |
Origin と Sec-Fetch-Site を検証する csrf() ミドルウェア |
Rails 8.2では、Sec-Fetch-Site だけを見る :header_only と、古いブラウザー向けにauthenticity tokenへfallbackする :header_or_legacy_token が用意されています。
Laravel 13では、まず Sec-Fetch-Site で同一オリジン性を確認し、通らない場合は従来のCSRFトークン検証へfallbackします。HTTPSでない場合はorigin verificationが利用できず、CSRFトークン検証へfallbackするとも説明されています。
Go 1.25の CrossOriginProtection は、ハンドラの前段でcross-origin checkを行い、失敗したリクエストを 403 Forbidden で拒否します。trusted originやbypass patternも設定できます。
Honoの csrf() は、Origin ヘッダーと Sec-Fetch-Site ヘッダーの両方を検査し、どちらかが通れば許可します。対象は unsafe method かつHTMLフォームで送信可能なcontent typeです。古いブラウザーやヘッダーが削られる環境では、別のCSRF token methodを使うよう注意されています。
自作ミドルウェアではどう判断するか
自分でHTTPミドルウェアを実装する場合、まず次の3点を決めます。
-
GET/HEAD/OPTIONSで状態変更しない -
same-siteを許可するか決める -
Sec-Fetch-Siteがない場合のfallbackを決める
最小の疑似コードは次のようになります。
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])
function shouldBlock(req: Request): boolean {
if (SAFE_METHODS.has(req.method)) return false
const site = req.headers.get('Sec-Fetch-Site')
if (site === 'cross-site') return true
if (site === 'same-origin') return false
if (site === 'same-site') {
return !trustSameSite()
}
// none / missing / unknown は fallback へ
return !passesFallback(req)
}
function passesFallback(req: Request): boolean {
return passesOriginCheck(req) || passesCsrfTokenCheck(req)
}
ユーザーエージェント文字列で分岐する必要はありません。
「Android 10以上なら許可」「このChromeなら許可」のように判定するのではなく、実際に届いた Sec-Fetch-Site、Origin、Referer、CSRFトークンを見て判断します。
設計方針の選び方
圧縮すると、選択肢は次の3つです。
| 方針 | 内容 | 向いている場面 |
|---|---|---|
| header-only |
Sec-Fetch-Site だけで判断 |
モダン環境に限定できる社内ツール |
| header + fallback |
Sec-Fetch-Site を見て、欠落時は Origin やCSRFトークンで確認 |
公開Webアプリ |
| 二重チェック |
Sec-Fetch-Site: cross-site を拒否し、それ以外でもCSRFトークン等を確認 |
決済、退会、権限変更、管理操作 |
公開Webアプリでは、まず header + fallback を標準候補にするのが無難です。
高リスク操作では、Sec-Fetch-Site だけに寄せず、CSRFトークン、再認証、確認画面などを残してよいです。
LPフォーム/APIではどう考えるか
静的LPとフォームAPIを同一オリジンに寄せられるなら、Sec-Fetch-Site ベースの防御と相性がよいです。
https://example.com/
https://example.com/api/contact
この構成では、LPからAPIへの投稿は same-origin になりやすいです。
一方、次のような構成では注意が必要です。
https://www.example.com/
https://api.example.com/contact
これは same-site にはなり得ますが、same-origin ではありません。same-site を許可するのか、Origin で https://www.example.com を明示的に許可するのか、設計判断が必要です。
ただし、未ログインの問い合わせフォームでは、CSRFよりもBotやスパムのほうが主問題です。Sec-Fetch-Site は、他サイトに置かれたフォームからの投稿を落とす入口防御にはなりますが、curlやBotからの直接POSTは防げません。
そのため、LPフォームAPIでは次の対策も組み合わせます。
- 入力バリデーション
- レート制限
- Bot対策
- ログ
- 通知先の制御
- エラー時の情報露出抑制
まとめ
Sec-Fetch-Site は、CSRF対策を「トークン照合」だけでなく、「リクエストの出所確認」として実装できるようにする重要なヘッダーです。
Baseline 2023 世代の機能として、主要ブラウザーでは十分に採用しやすくなっています。
しかし、Baseline 2023 に含まれるからといって、CSRFトークンを即廃止できるわけではありません。古いAndroid端末、WebView、アプリ内ブラウザー、ヘッダー欠落環境を考えるなら、公開Webアプリではfallbackを残すのが現実的です。
実務では、次の方針が安全です。
まず
Sec-Fetch-Site: cross-siteの unsafe request を入口で拒否する。
ヘッダーがない場合は、Origin検証やCSRFトークンへfallbackする。
高リスク操作では、CSRFトークンや再認証などの追加確認を残す。
CSRFトークンを使うか使わないかだけでなく、どのオリジンを信頼するのか、same-site を許可するのか、古い環境をどこまでサポートするのかを決めることが重要です。