---
title: "Invalid CSRF tokenの意味と直し方｜Laravel 419・Django・Spring・Expressのエラー別に切り分け"
url: "https://www.issoh.co.jp/tech/details/3031/"
published: 2024-07-11
updated: 2026-10-18
categories: ["セキュリティ"]
publisher: "株式会社一創"
---

# Invalid CSRF tokenの意味と直し方｜Laravel 419・Django・Spring・Expressのエラー別に切り分け

フォームを送信した瞬間に「Invalid CSRF token」「intercepted」と英語で返され、入力内容ごと画面が消える。このメッセージは、サーバーが「このリクエストが本当に自サイトの画面から送られたのか確認できない」と判断し、処理に入る前に遮断したことを意味します。攻撃を受けたサインに見えますが、正規の操作でもセッション切れ・Cookieのブロック・古いタブといった利用者側の事情で発生します。この記事では、メッセージそのものの読み方から、Laravel・Django・Spring Security・Rails・Expressが返す具体的な文言、TikTokや業務SaaSの画面で出た場合の原因、利用者と開発者それぞれの切り分け手順までを整理します。

## まとめ：Invalid CSRF tokenエラーの意味と最短の直し方

Invalid CSRF tokenは「照合用の文字列（CSRFトークン）が検証条件を満たさない、または付いていない」というサーバー側の判定結果です。続く`intercepted`は、そのリクエストがアプリケーションの処理に届く前に遮断されたことを指します。遮断されたリクエストによる更新は通常行われていません。ただし、それ以前の操作まで取り消されるわけではないので、送金や注文の途中だったなら履歴を確認してから操作し直してください。

利用者として遭遇したなら、フォームのページ自体を再読み込みしてから入力し直すのが最短の復旧手順になります。別タブでのログインなどでトークンが更新されていれば、古いタブに残った画面をそのまま送信しても何度でも弾かれます。それでも直らなければ、Cookieをブロックする拡張機能とプライベートモードを疑ってください。

開発者として原因を追うなら、見る順番は決まっています。セッションの有効期限、複数サーバー構成でのセッション共有、リバースプロキシ配下のオリジン設定、そしてフォームを含むページのキャッシュ。これに、トークンの付け忘れや送信形式の不一致を加えて確認します。ローカル開発だけで出るなら、同じホストで動く別アプリとのCookie名の衝突も候補に入ります。以降、メッセージの構造、フレームワーク別の正体、サービス画面で出る場合、原因、直し方の順に掘り下げます。

Laravelでセッションの保存先そのものをどう選ぶかは、[Laravelのセッション管理｜ドライバ選定とCSRF・複数台構成の共有設計](/tech/details/17152/)で整理しています。

## 「Invalid CSRF token」「intercepted」というメッセージが指しているもの

エラー文が英語のまま出るため、何を咎められているのか掴みにくいのがこのメッセージの厄介なところです。単語ごとに分解すると、サーバーが何を確認して何に失敗したのかがはっきりします。

### invalid・CSRF token・interceptedの語義分解

**CSRF token**は、リクエストが自サイトの画面から送られたことを確かめるための値です。フォームへの埋め込みやCookie・専用APIでの受け渡しなど渡し方は実装ごとに違い、セッション中は同じ値を使い続ける方式も、ログインのたびに作り直す方式もあります。典型的なフォームでは、ブラウザが送信時にこの値を一緒に返し、サーバーは自分が発行した値と一致するかを照合します。外部サイトに置かれた罠のフォームはこの値を知り得ないため、照合を通せません。

**invalid**は、その照合に失敗した状態を指します。値が違う場合だけでなく、そもそも送られてこなかった場合、期限切れで破棄済みだった場合も同じ「invalid」に丸められる実装が大半です。だから「トークンは確かに送ったのに invalid と言われる」という状況が普通に起こります。

**intercepted**は「途中で捕まえた」という意味です。CSRF検証はコントローラーやビューの手前に置かれたミドルウェア／フィルタが担当するため、多くの実装では業務ロジックが動く前にリクエストが打ち切られます。ただし、遮断されるのはそのリクエストだけで、それ以前のリクエストで成立した送金や登録が取り消されるわけではありません。

なお、`Invalid CSRF token intercepted.`という一文は、この記事で扱うLaravel・Django・Spring Security・Rails・csurf・csrf-sync・Symfony・Flask-WTFのCSRF処理のソースには含まれていません（各フレームワークの該当ファイルで`intercepted`を検索して確認）。文言だけでは実装元を特定できないため、表示された製品名と運営元のヘルプを手掛かりに調べ、対処は下記の一般的な原因から当たります。

### 攻撃の兆候か誤検知かを見分ける条件

エラー画面が出たという事実だけでは、攻撃か正規操作の失敗かは判定できません。CSRF攻撃には、罠ページを開いた瞬間に背後で送る形のほか、メールや外部サイトのリンク・フォームを利用者に操作させる形もあり、その場合も遷移先にエラーが表示されます。見分けの手掛かりは送信元です。自分でそのサイトを開き、そのサイトのフォームから送信して出たなら、正規操作の失敗である可能性が高い。外部サイトやメールのリンクを踏んだ直後に出たなら、防御が攻撃を止めた可能性を考えます。なお、罠ページ上で本人にボタンを押させる[クリックジャッキング](/tech/details/16360/)はトークン照合を通過してしまうため、このエラーでは検知できません。

運営者の側では、失敗理由、対象URL、Origin・Referer、発生時刻、認証状態、送信元IPを突き合わせます。特定のIPから短時間に検証失敗が続いていれば警戒材料になりますが、単一IPの連続失敗だけでは攻撃の有無は判定できません。トークンがどう発行され照合されるのかは[CSRFトークンとは？仕組み・Double Submit CookieとSPAでの実装](/column/details/3984/)で整理しました。混同されやすいXSSとの守備範囲の違いは[XSSとCSRFの違いとは？仕組み・被害・対策を実装レベルで徹底比較](/tech/details/4109/)にまとめてあります。

## フレームワーク別に見るCSRFエラーの正体とHTTPステータス

表示される文言はフレームワークによってまるで違います。同じ現象なのに検索しても情報が噛み合わないのは、この差が原因です。手元のエラーがどれに当たるかを先に特定すると、調査範囲が一気に狭まります。

| フレームワーク         | 画面・ログに出る文言                                 | ステータス    |
| --------------- | ------------------------------------------ | -------- |
| Laravel         | Page Expired / CSRF token mismatch.        | 419      |
| Django          | CSRF verification failed. Request aborted. | 403      |
| Spring Security | Invalid CSRF Token ‘…’ was found on …      | 403      |
| Rails 8.1       | Can’t verify CSRF token authenticity.      | 422      |
| Express（csurf系） | ForbiddenError: invalid csrf token         | 403      |
| Symfony         | Invalid CSRF token.                        | ログイン失敗扱い |
| Flask-WTF       | The CSRF token is missing. ほか              | 400      |

### Laravel：419 Page Expiredとバージョンで入れ替わるミドルウェア

Laravelでトークン照合に失敗すると`Illuminate\Session\TokenMismatchException`が投げられます。メッセージは`CSRF token mismatch.`です。フレームワークの例外ハンドラがこれをHTTP 419へ変換しており、Handler.phpには`$e instanceof TokenMismatchException => new HttpException(419, $e->getMessage(), $e)`という一行があります。419という見慣れないステータスの出どころはここです。画面に出るのは「Page Expired」になります。

照合を担うミドルウェアは、メジャーバージョンごとに名前も除外方法も変わりました。手元のプロジェクトで`VerifyCsrfToken`が見つからないなら、原因は改名です。改名後のクラスがどこに登録され、どの順で走るのかは[Laravelのミドルウェアとは｜適用範囲の使い分けと実行順序の決め方](/tech/details/17158/)で扱っています。

| バージョン | ミドルウェア                                                          | 除外の書き方                         |
| ----- | --------------------------------------------------------------- | ------------------------------ |
| 10    | App\\Http\\Middleware\\VerifyCsrfToken                          | $except プロパティ                  |
| 11・12 | Illuminate\\Foundation\\Http\\Middleware\\ValidateCsrfToken     | validateCsrfTokens(except:)    |
| 13    | Illuminate\\Foundation\\Http\\Middleware\\PreventRequestForgery | preventRequestForgery(except:) |

Laravel 13の新しいミドルウェアは、まずブラウザが送る`Sec-Fetch-Site`ヘッダーを見ます。同一オリジンからのリクエストだと分かればトークン検証を行わずに通し、判定できないときだけ従来のトークン照合へ落とす二段構えです。[Laravel 13.xの公式ドキュメント](https://laravel.com/docs/13.x/csrf)は「The `Sec-Fetch-Site` header is only sent by browsers over secure (HTTPS) connections」と明記しており、HTTPで動く開発環境ではこの短絡路が働きません。ローカルだけ419が出るという食い違いは、ここから生まれます。サブドメイン間でフォームを送る構成なら`allowSameSite: true`で同一サイトからの送信も許可できます。

オリジン検証だけに寄せてトークンを廃止することも可能です。この場合、失敗時のステータスは419ではなく403になります。

```
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(originOnly: true);
})
```

Ajax経路で詰まる場合は、トークンの運び方が2系統あることを押さえてください。`meta`タグの値を`X-CSRF-TOKEN`ヘッダーへ載せる方法と、Laravelが毎レスポンスで発行する`XSRF-TOKEN`クッキーの値を`X-XSRF-TOKEN`ヘッダーへ載せる方法です。AxiosやAngularは後者を自動で行います。ただしこのクッキーは暗号化されており、暗号化キー（`APP_KEY`）を変更したり環境ごとに違う値を配ったりすると、発行済みのクッキーは一斉に復号できなくなります。デプロイ直後だけ419が多発するときは、まずキーの取り違えを疑ってください。

### Django：CSRF verification failed.とREASON定数の読み方

Djangoの`CsrfViewMiddleware`は検証に失敗するとHTTP 403を返し、画面には「Forbidden」という見出しと「CSRF verification failed. Request aborted.」という本文を出します。日本語ロケールの環境で「CSRF検証に失敗したため、リクエストは中断されました。」と表示されるのが、この`main`にあたる文言です。

Djangoは失敗理由を定数として細かく分けており、原因の絞り込みが速く進みます。[django/middleware/csrf.py（6.1系）](https://github.com/django/django/blob/stable/6.1.x/django/middleware/csrf.py)には次の文字列が並びます。

- `CSRF cookie not set.`／`CSRF token missing.` ── Cookieまたはトークン自体が届いていない
- `CSRF token from POST incorrect.` ── 届いたトークンがCookie側の値と合わない（ヘッダー経由なら`POST`の部分がヘッダー名を含む表記に変わる）
- `Origin checking failed - %s does not match any trusted origins.` ── Originヘッダーが`CSRF_TRUSTED_ORIGINS`と一致しない
- `Referer checking failed - no Referer.`／`Referer checking failed - Referer is insecure while host is secure.` ── HTTPS環境でRefererが欠落、またはhttpから来ている
- `has incorrect length`／`has invalid characters` ── トークンの形が壊れている（途中で加工された疑い）

ところがこの理由は`DEBUG = True`のときしか画面に出ません。本番でエラーだけを見ても原因は分からないので、ログ側で拾います。CSRF拒否は`django.security.csrf`ロガーに警告として記録されるため、出力先を繋いでおけば「Cookieが無いのか、Originが違うのか」を一発で切り分けられます。

```
# settings.py
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {"console": {"class": "logging.StreamHandler"}},
    "loggers": {
        "django.security.csrf": {"handlers": ["console"], "level": "WARNING"},
    },
}
```

Originの不一致で詰まるなら、確認するのは`CSRF_TRUSTED_ORIGINS`の書き方です。Django 4.0以降、この設定値にはスキームを含める必要があります。`example.com`ではなく`https://example.com`と書きます。HTTPS終端をリバースプロキシやロードバランサに置いた構成で、アップグレード後に突然403が出るようになったなら、まずここを見てください。HTTPS運用では`CSRF_COOKIE_SECURE = True`も併せて設定し、クッキーを置きたくない場合は`CSRF_USE_SESSIONS = True`でセッション側に保管する選択肢もあります。

Django REST FrameworkのSessionAuthenticationを使っている場合、返ってくる文言が変わります。DRFは`rest_framework/authentication.py`で`CSRF Failed: (理由)`という形式の403を返すため、Djangoの403ページは表示されません。APIだけがエラーになるならDRF側の経路を疑ってください。

### Spring Security：「Invalid CSRF token found for」ログと’null’の読み方

Spring Securityで最初に目に入るのは、たいていログの1行です。[CsrfFilter](https://github.com/spring-projects/spring-security/blob/7.1.x/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java)は照合に失敗すると、DEBUGレベルで`Invalid CSRF token found for http://…`とリクエストURLを出力します。このログは値が違う場合にも、サーバー側にトークンが無い場合にも同じ文面で出るため、これだけでは原因を絞れません。分かれ目はその直後に作られる例外です。

| 例外                        | メッセージ                                      | 意味                  |
| ------------------------- | ------------------------------------------ | ------------------- |
| InvalidCsrfTokenException | Invalid CSRF Token ‘null’ was found …      | トークンが無い、または値を解読できない |
| InvalidCsrfTokenException | Invalid CSRF Token ‘(値)’ was found …       | 付けた値が保存値と違う         |
| MissingCsrfTokenException | Could not verify the provided CSRF token … | 保存先から既存トークンを取れない    |

`'null'`には2つの意味があります。1つはフォームの`_csrf`パラメータかヘッダーの`X-CSRF-TOKEN`が欠けている場合で、Thymeleafを使わない手書きのフォームや、ヘッダーを付け忘れたfetchで起きます。もう1つは値が届いていても解読できなかった場合です。既定の`XorCsrfTokenRequestAttributeHandler`は、受け取った値の解読に失敗すると`null`を返します（[7.1.xのソース](https://github.com/spring-projects/spring-security/blob/7.1.x/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java)）。後述するSPAの生の値の送信はこちらに当たります。`MissingCsrfTokenException`は、保存先（既定はセッション、`CookieCsrfTokenRepository`ならCookie）から既存のトークンを取得できず、フィルタがその場で新規生成したことを示します。セッション切れ、複数台構成でのセッション非共有、Cookieの欠落を疑う場面です。DEBUGログを出すには`logging.level.org.springframework.security=DEBUG`を設定します。

SPA構成ではもう1つ罠があります。Spring Security 6以降の既定ハンドラは`XorCsrfTokenRequestAttributeHandler`で、BREACH攻撃対策としてトークン値にランダム性を混ぜ、リクエストごとに見た目が変わるよう符号化しています。一方、`CookieCsrfTokenRepository`が`XSRF-TOKEN`クッキーへ書き込むのは符号化前の生の値です。公式リファレンスも「JavaScript applications will only have access to the plain token value and will not have access to the encoded value」と書いています。JavaScriptがクッキーの値をそのまま`X-XSRF-TOKEN`ヘッダーへ載せると、サーバーは符号化された値を期待しているため照合に失敗します。

さらに、認証成功時とログアウト成功時には`CsrfAuthenticationStrategy`と`CsrfLogoutHandler`が古いトークンを破棄します。Spring Securityは既定でトークンの読み込みを遅延させるため、そのままでは新しいクッキーが返りません。ログイン直後だけ通らない、という再現しにくい形で表面化します。

解決策はバージョンで分かれます。**Spring Security 7.0以降**は`csrf.spa()`の一行で、クッキーの扱いと認証後のトークン再発行がまとめて解決します。**6.x系にこのメソッドはありません**（`CsrfConfigurer#spa()`は`@since 7.0`）。6.xでは、[6.5系の公式リファレンス](https://docs.spring.io/spring-security/reference/6.5/servlet/exploits/csrf.html)に掲載されたクラス定義をプロジェクトへ追加し、ヘッダー経由なら生の値、パラメータ経由なら符号化値を使い分ける`SpaCsrfTokenRequestHandler`として差し込みます。

```
// Spring Security 6.x（7.0 以降なら csrf.spa() の一行で済む）
http.csrf((csrf) -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
);
```

Spring Securityで認証処理そのものを組む手順は、[Spring Bootの認証実装：カスタムAuthenticationProviderの作り方とテスト【Spring Security 7.1対応】](/tech/details/2467/)で扱っています。

### Rails・Express・Symfony・Flask-WTFのエラー文言とHTTPステータス

ほかの主要フレームワークも仕組みは同じで、違うのは文言とステータスだけです。検索に使う語を取り違えないよう、ソースコード上の文字列を並べます。

**Rails**は8.1系で`ActionController::InvalidAuthenticityToken`を送出し、ログに`Can't verify CSRF token authenticity.`と記録します。[ExceptionWrapper](https://github.com/rails/rails/blob/v8.1.4/actionpack/lib/action%5Fdispatch/middleware/exception%5Fwrapper.rb)がこの例外を422へ対応づけているため、本番では422のエラーページになります。開発中の次期版（mainブランチ）では`InvalidCrossOriginRequest`への改名が入り、旧名は「Rails 9.0で削除予定」と書かれています。版を上げたら`rescue_from`のクラス名を確認してください。

**Express**には標準のCSRF対策が無く、ミドルウェアを足します。定番だった`csurf`は1.11.0を最後に、npm上で「This package is archived and no longer maintained.」と表示されるアーカイブ状態です。後継の[csrf-csrf](https://github.com/Psifi-Solutions/csrf-csrf)や`csrf-sync`も同じ形のエラーを投げ、既定では`ForbiddenError: invalid csrf token`、ステータス403、`err.code`は`EBADCSRFTOKEN`です。express 5.3.0とcsrf-sync 4.2.1で、トークン無しのPOSTが403とこの文言のスタックトレースを返すこと、次のエラーハンドラで利用者向けの文面に差し替えられることを確認しました。

```
// CSRFエラーだけを拾い、利用者に再読み込みを促す
app.use((err, req, res, next) => {
  if (err.code !== "EBADCSRFTOKEN") return next(err);
  res.status(403).send("送信内容の検証に失敗しました。ページを再読み込みしてから送信し直してください。");
});
```

**Symfony**は2系統あります。ログインフォームでは`InvalidCsrfTokenException`のメッセージキー`Invalid CSRF token.`が認証失敗の理由として返り、通常のフォームでは`The CSRF token is invalid. Please try to resubmit the form.`がフォームエラーとして表示されます（8.1系のソースで確認）。**Flask-WTF**は`CSRFError`が400を返し、理由は`The CSRF token is missing.`・`The CSRF token has expired.`・`The CSRF tokens do not match.`などに分かれます。Flask-WTFはトークン自体に有効期限があり、`WTF_CSRF_TIME_LIMIT`の既定は3600秒（60分）です。ログイン状態が続いていても、1時間開いたままのフォームは弾かれます。

### 主要フレームワークのどれでもない「CSRF token verification failed.」の出どころ

検索でよく見かける`CSRF token verification failed.`という文言は、上のフレームワークのいずれも出力しません。Djangoの`CSRF verification failed.`と似ていますが、`token`の1語が入るかどうかで別物です。この文字列を出すのは、たとえばZammadのように自前で例外メッセージを定義しているアプリケーションです。文言が一致しないなら、フレームワーク名ではなく製品名と組み合わせて検索したほうが確実に辿り着けます。

## サービスやアプリの画面にInvalid CSRF tokenが出る場合

このエラーは開発中のアプリだけでなく、TikTokや業務SaaS、大学の学習支援システムなど、利用者として使っているサービスの画面でも表示されます。運営元が原因を公開している例を並べると、利用者の手元で直せる範囲がはっきりします。

### 運営元のヘルプが挙げる原因：Cookieのブロックと拡張機能

契約管理SaaSのIroncladは、[ヘルプ記事](https://support.ironcladapp.com/hc/en-us/articles/13668976714391-Handle-an-Invalid-CSRF-Token-Error-Message)で自社画面のCSRFエラーを「This is most likely caused by an advertisement or script-blocking plugin」と説明し、ブラウザ別にCookieを許可する手順を載せています。東海国立大学機構の学習支援システムTACTの[ヘルプ](https://tact-help.ac.thers.ac.jp/hc/ja/articles/46800119201561)も、「CSRFトークンの検証に失敗しました。」の原因としてCookieの無効化を挙げ、改善しなければ拡張機能（Monica、Comfortable Sakaiなど）を無効化するよう案内しています。製品は違っても、運営元が最初に疑うのはCookieが保存・送信されない状態です。

### AWS Cognitoのマネージドログイン：古いタブからのログイン

ログイン画面を2つのタブで開き、先に開いたほうでログインすると「Invalid CSRF token」になる。[AWS re:Postの質問](https://repost.aws/questions/QUOIKwjxR8S0aC5Y8md772FA/why-does-aws-cognito-managed-login-return-invalid-csrf-token-when-login-is-attempted-from-an-earlier-tab-window)では、この現象について質問者が「後から開いたタブがCognitoドメインの`XSRF-TOKEN`クッキーを上書きし、先のタブが持つ値と合わなくなる」と分析しています。回答欄には、ログイン画面を翌朝まで開いたままにしても同じエラーになるという報告もあります。共有のCookieが後から開いた画面で上書きされ、古い値を受け付けない実装なら、先に開いた画面からの送信は失敗します。利用者側の対処は、開いているログイン画面をすべて閉じ、アプリから1回だけ開き直すことです。

### SillyTavernなどローカルで動かすWebアプリ：同じホストのCookie衝突

自分のPCで動かすWebアプリでも出ます。AIチャットのフロントエンドであるSillyTavern 1.19.0は、`csrf-sync`のエラーメッセージを`Invalid CSRF token. Please refresh the page and try again.`に差し替えています（[src/server-main.js](https://github.com/SillyTavern/SillyTavern/blob/release/src/server-main.js)）。セッションはCookieに保存され、Cookie名はPCのホスト名から作った`session-`＋8桁の値です。

ここで効いてくるのが、Cookieがポート番号で分離されないという仕様です。[RFC 6265の8.5節](https://www.rfc-editor.org/rfc/rfc6265#section-8.5)は「Cookies do not provide isolation by port.」と明記しています。同じPCで別ポートの同じアプリを2つ起動し、両方の画面をブラウザで開くと、どちらも同じ名前・Domain・PathのCookieを発行するため、後から応答を受け取ったほうがブラウザ上の既存Cookieを上書きします。開発者でも同じことが起こり、`localhost:8000`と`localhost:8001`で別々のDjangoプロジェクトを動かすと、どちらも既定のCookie名`csrftoken`と`sessionid`を使うため取り合いになります。Laravel 13の既定のセッションCookie名も`APP_NAME`から作る`laravel-session`です。ローカルだけで再現し本番では出ないなら、同じホストの別ポートで動いているアプリを止めて確かめてください。

SillyTavernには`--disableCsrf`という起動オプションがありますが、設定ファイル側の項目にも「NOT RECOMMENDED」と書かれています。外部から接続できる状態で使うなら、無効化ではなくCookieの衝突を解消してください。

## 攻撃ではないのにエラーが出る典型原因

正規の操作でも、トークンやCookieが検証条件を満たさなければ拒否されます。防御機構としては条件どおりに動いているので、直す対象は条件を満たせなかった理由のほうです。発生源は利用者の環境側とサーバー構成側に分かれ、切り分けの入口も違います。

### セッション切れ・Cookieブロック──利用者環境側の原因

原因の1つ目は有効期限切れです。フォームを開いたまま放置し、トークンに結び付くセッションやトークン自体の期限を過ぎてから送信すると、検証に失敗します。期限の決まり方は実装で大きく違います。Laravelはトークンをセッションに保存し、既定のセッション寿命は`config/session.php`の`lifetime`で120分です。Djangoは既定（`CSRF_USE_SESSIONS = False`）でトークンをセッションと独立した`csrftoken`Cookieに置き、`CSRF_COOKIE_AGE`の既定は約1年なので、放置だけでは失効しにくい設計です。前述のFlask-WTFはトークン自体が60分で切れます。「あのサイトでは平気なのに」という体感の違いは、この設計差から生まれます。

ログインに伴うトークン更新も原因になります。Djangoの403ページには`DEBUG = True`のときだけ表示されるHelp欄があり、そこに「After logging in in another browser tab or hitting the back button after a login, you may need to reload the page with the form, because the token is rotated after a login.」という一文が置かれています。別タブでログインし直した、ログイン後に戻るボタンで前の画面へ帰った。どちらもトークンが回転済みで、古いフォームは無効です。

必要なCookieが保存・送信されない環境も原因になります。広告ブロックやトラッキング防止の拡張機能、サイト単位でCookieを拒否する設定、ログイン画面が別ドメインにある構成でのサードパーティCookieの制限が代表例です。トークンやセッションの保管先が使えなければ、送信しても照合相手が存在しません。プライベートウィンドウでも閲覧中のCookieは保持されるため、使うだけで必ず失敗するわけではありません。保存領域ごとの挙動差は[IndexedDBのデメリットと容量上限｜localStorage・Cookieとの違いと使い分け](/tech/details/9666/)で比較しています。

### セッション共有・キャッシュ──サーバー構成側の原因

複数台構成でセッションを共有していないケースが筆頭です。トークンを発行したサーバーと送信先になったサーバーが別だと、照合相手がどこにも無い状態になります。負荷が低い時間帯は再現せず、アクセスが増えて振り分けが分散した途端に多発する、という出方をします。RedisやDBへセッションを外部化すれば解消します。

フォームを含むページがCDNやリバースプロキシにキャッシュされている構成も危険です。ある利用者向けに発行されたトークンが埋まったHTMLを、別の利用者が受け取ります。ログイン後の画面とフォームを含むページは、キャッシュ対象から確実に外してください。

クッキー属性の設定ミスも見落とされがちです。認証Cookieに`SameSite=Strict`を付けると、外部サイトのリンクから来た最初のリクエストにはCookieが送られません。その時点で返る画面がログアウト状態や別セッションのトークンで描かれると、続く送信が照合に失敗することがあります。決済サービスや外部IDプロバイダからのリダイレクトを挟む導線では、`Lax`が現実的な落としどころです。`Secure`属性を付けたCookieはHTTPS以外で送信されないため、HTTPのままの検証環境だけ再現するというねじれも起こります。なお、OriginヘッダーやCORSの設定と混同されやすい領域でもあります。両者の役割分担は[CORSとは？仕組み・プリフライト・サーバー設定例・エラー解決を実装目線で解説](/tech/details/13763/)で整理しました。

## エラーを直すための切り分け手順

利用者は次の復旧手順を、開発者はその後のログとリクエストの確認手順を参照してください。

### 利用者がその場でできる復旧手順

入力内容をコピーして退避してから、次の順に試します。手間の小さいものから並べています。Cookieを削除するときは、ブラウザ全体ではなく対象サイトの分だけに限ってください。

- フォームのページを再読み込みし、新しい画面で入力し直して送信する
- 同じサイトを開いている他のタブを閉じ、1つのタブだけで操作する
- そのサイトのCookieを削除し、ログインからやり直す
- 広告ブロックやプライバシー保護系の拡張機能を、そのサイトだけ無効にする
- プライベートウィンドウや別ブラウザで再現するか確かめる

ここまでで直らず、他の利用者にも同時に起きているなら、サイト側の問題か、社内プロキシなど共通のネットワーク・ブラウザ設定が疑われます。発生時刻と操作手順を添えて運営元に問い合わせてください。

### 開発者が原因を特定する順序

まず失敗理由を可視化します。Djangoなら前述のログ設定、Laravelなら419を返したリクエストのセッションID、Spring Securityなら例外が`InvalidCsrfTokenException`と`MissingCsrfTokenException`のどちらか、Expressなら`err.code`。失敗理由を確認する前に設定を変えると、どの変更が原因に効いたのか判断できなくなります。

次にブラウザの開発者ツールで、送信されたリクエストの中身を実物で確認しましょう。見るのは3点です。トークンのフィールドまたはヘッダーが付いているか、Cookieが同時に送られているか、Originヘッダーのスキーム・ホスト・ポートが許可したオリジンと一致しているか。フィールドはあるのにCookieが無ければ保存側、両方あるのに落ちるならサーバー側の保管・共有を疑います。

再現条件が掴めたら、セッションストアの構成と対象ページのキャッシュ設定を確認します。仕上げに、採用した防御方式に応じた拒否テストを行います。トークン方式では欠落・不一致のリクエストが、Laravel 13のオリジン検証ではクロスサイトの更新要求が拒否されることを確認します。[OWASP ZAPの診断項目とは？検出できる脆弱性一覧とOWASP Top 10 2025対応・使い方](/tech/details/2958/)で扱うような診断ツールを使うと、更新系エンドポイント全体に検証が掛かっているかをまとめて点検できます。社内で点検の手が回らないなら、[脆弱性診断・セキュリティ診断](https://www.issoh.co.jp/service/system/vulnerability/)として外部に任せる方法もあります。

## CSRF検証を外してよい場面と、外してはいけない場面

エラーが止まらないとき、検証そのものを無効化する対処が検索上位にしばしば現れます。ここは立場をはっきりさせておきます。除外を検討してよいのは、ブラウザが自動で送る資格情報（Cookie）に依存せず、別の認証・検証で保護されたエンドポイントだけです。

正当に除外できる代表例が、決済サービスなどからのWebhookです。外部システムはこちらのトークンを知りようがなく、そもそもブラウザのCookieを伴わないため、CSRFの成立条件から外れています。Laravelの公式ドキュメントもStripeのWebhookを例に挙げ、除外設定を案内しています。除外した受信口では、Stripeの署名検証のように送信元が提供する仕組みでリクエストの正当性を別途確認してください。

```
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'stripe/*',
    ]);
})
```

Authorizationヘッダーでトークンを渡すAPIも同様です。ブラウザが自動で付与する資格情報に依存していない以上、CSRFトークンを重ねる意味はありません。この方式との違いは[JWTとは？構造・署名検証の仕組みとセッション・OAuth/OIDCとの違いを実装視点で解説](/tech/details/13453/)で扱っています。

逆に、管理画面・決済・退会・権限変更で検証を外すのは、規模を問わず選択肢に入りません。「一時的に無効化して様子を見る」も避けます。原因が設定不備でも期限切れでも、無効化は原因を解消せず、防御だけを失わせます。切り分けが長引くなら、無効化ではなくログを増やす方向へ進めてください。なお、テスト実行時についてはLaravelが全ルートでCSRFミドルウェアを自動的に無効化します。テストを通すために本番設定へ手を入れる必要はありません。

## よくある質問

### 「invalid csrf token intercepted.」とはどういう意味ですか？

「照合用のCSRFトークンが一致しなかったため、このリクエストを処理の手前で遮断した」という意味です。遮断されたリクエストによる更新は通常行われていませんが、送金や注文の途中だったなら履歴を確認してください。この英文はLaravel・Django・Spring Securityなど主要フレームワークのソースには無く、文言から実装元は特定できません。正規の操作でもセッション切れやCookieのブロックで起きるので、まずページを再読み込みして入力し直してください。

### このエラーが出たら攻撃を受けているのでしょうか？

表示されたことだけでは判定できません。CSRF攻撃は背後での自動送信のほか、悪意あるリンクやフォームを操作させる形でも起きるため、エラー画面が見える場合もあります。自分でそのサイトを開いて送信したのか、外部サイトやメールのリンクを踏んだ直後なのかを確認してください。後者なら、防御が不正なリクエストを止めた可能性があります。運営者は失敗理由・Origin・送信元IP・発生時刻をログで突き合わせます。

### TikTokで「Invalid CSRF token」と表示されたらどうすればよいですか？

TikTokの内部実装は公開されていないため、利用者側で切り分けられる範囲で対処します。ブラウザ版なら、開いているTikTokのタブを1つに絞って再読み込みし、直らなければtiktok.comのCookieを削除してログインし直してください。広告ブロックなどの拡張機能を止める、別のブラウザで試す、アプリならアプリを最新版に更新する、の順で試します。TikTok固有の発生条件は公開情報では確認できないため、それでも直らなければ発生時刻と操作手順を添えてTikTokのサポートへ報告してください。

### Laravelの419エラーとCSRFエラーは同じものですか？

実質的に同じです。Laravelはトークン照合に失敗すると`CSRF token mismatch.`というメッセージの`TokenMismatchException`を投げ、例外ハンドラがHTTP 419へ変換して「Page Expired」と表示します。ただしLaravel 13でオリジン検証のみのモードを有効にした場合、失敗時のステータスは419ではなく403になります。

### 「CSRF検証に失敗したため、リクエストは中断されました。」とはどういう意味ですか？

Djangoの403エラーページで、英語表記の「CSRF verification failed. Request aborted.」にあたります。原因の詳細は`DEBUG = True`のときしか画面に出ないため、本番環境では`django.security.csrf`ロガーの警告を確認してください。Cookie未設定・トークン欠落・Origin不一致のどれなのかが理由として記録されています。Django REST Framework経由なら`CSRF Failed:`という別形式になります。

## 関連記事

- [CSRFトークンとは？仕組み・Double Submit CookieとSPAでの実装](/column/details/3984/)
- [XSSとCSRFの違いとは？仕組み・被害・対策を実装レベルで徹底比較](/tech/details/4109/)
- [Spring Bootの認証実装：カスタムAuthenticationProviderの作り方とテスト【Spring Security 7.1対応】](/tech/details/2467/)
- [Laravelのセッション管理｜ドライバ選定とCSRF・複数台構成の共有設計](/tech/details/17152/)
- [CORSとは？仕組み・プリフライト・サーバー設定例・エラー解決を実装目線で解説](/tech/details/13763/)

---

出典: [Invalid CSRF tokenの意味と直し方｜Laravel 419・Django・Spring・Expressのエラー別に切り分け](<https://www.issoh.co.jp/tech/details/3031/>)（株式会社一創）
