CSRF 対策
ほかのサイトがログイン中の人になりすまして操作する「CSRF」という攻撃と、Laravel がそれを防ぐしくみ、フォームに書く @csrf の意味を説明します。
CSRF(シーエスアールエフ、クロスサイト・リクエスト・フォージェリ)は、悪いサイトが、あなたのアプリにログイン中の人になりすまして、勝手に操作を送りつける攻撃です。Laravel は、この攻撃を最初から防いでくれます。ふだん気をつけるのは、フォームに @csrf と1行書くことだけです。
どんな攻撃なのか#
たとえば、あなたのアプリに「メールアドレスを変える」ページがあり、/user/email へ POST(データを送る種類のリクエスト)で新しいアドレスを送ると変わるとします。
悪い人は、自分のサイトに、あなたのアプリへ向けて送信するフォームを隠して置いておきます。
<form action="https://your-application.com/user/email" method="POST">
<input type="email" value="malicious-email@example.com">
</form>
<script>
// ページを開いたとたんに、フォームを送信してしまう
document.forms[0].submit();
</script>
あなたのアプリにログインしたままの人が、このサイトを開いただけで、フォームが勝手に送られます。ブラウザはログインの情報もいっしょに送るので、アプリは本人の操作だと思いこみ、メールアドレスが悪い人のものに変わってしまいます。
これを防ぐには、POST・PUT・PATCH・DELETE(データを変えるリクエスト)が届くたびに、「悪いサイトには分からない合言葉」が付いているかを確かめればよい、というのが基本の考え方です。
Laravel が防ぐしくみ#
routes/web.php に書いたルートには、最初から PreventRequestForgery というミドルウェア(リクエストが処理に届く前に、間に入って確かめる係)がかかっています。この係は、2段構えで確かめます。
- どこから来たリクエストかを見る:いまのブラウザは、リクエストに
Sec-Fetch-Siteという印を自動で付けます。「自分のアプリと同じオリジン(https://などの種類・ドメイン・ポートの組)から来た」と分かれば、そのまま通します - 合言葉(CSRF トークン)を見る:1つ目で判断できないとき(古いブラウザで印が無い、HTTPS でない、など)は、合言葉が正しいかで確かめます
合言葉は「CSRF トークン」と呼ばれる長い文字列です。Laravel がセッション(同じ人のアクセスをまたいで情報を覚えておくしくみ)ごとに作り、セッションの中にしまいます。セッションを作り直すたびに変わるので、悪いサイトがこの値を知ることはできません。
いまのトークンは、次のどちらでも取り出せます。
use Illuminate\Http\Request;
Route::get('/token', function (Request $request) {
// リクエストのセッションから取り出す
$token = $request->session()->token();
// ヘルパー関数で取り出す(中身は同じ)
$token = csrf_token();
// ...
});
フォームには @csrf を書く#
POST・PUT・PATCH・DELETE で送るフォームを作るときは、フォームの中に @csrf と書きます。これは Blade のディレクティブ(@ で始まる命令)で、合言葉を入れた隠し入力欄に置きかわります。
<form method="POST" action="/profile">
@csrf
<!-- @csrf は、次の1行と同じものになる -->
<input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>
注意
@csrf を書き忘れると、1つ目の確かめ方で通らなかったときに、送信が「419」というエラーで止まります。フォームを送って 419 が出たら、まず @csrf があるかを見てください。
補足
Laravel を API(ほかのプログラムからデータを受け取る窓口)としてだけ使い、画面は別のプログラム(SPA)で作る場合は、Laravel Sanctum というパッケージのやり方に従います。
来た場所だけで確かめる(origin のみのモード)#
ふつうは、1つ目(どこから来たか)で判断できなければ、2つ目(合言葉)で確かめます。合言葉を使わず、来た場所だけで確かめたいときは、bootstrap/app.php で次のように書きます。
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(originOnly: true);
})
このモードで確かめに失敗すると、419 ではなく 403(禁止)のエラーが返ります。
注意
Sec-Fetch-Site の印は、ブラウザが HTTPS でつないだときにしか付けません。HTTPS でないアプリでは来た場所を確かめられないので、合言葉で確かめる方法に戻ります。
サブドメイン(example.com と dashboard.example.com のような、同じサイトの別の名前)から来たリクエストも通したいときは、allowSameSite を使います。
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(allowSameSite: true);
})
確かめない URL を決める#
外のサービスから送られてくるリクエストは、合言葉を知りようがありません。たとえば Stripe(支払いのサービス)が、支払いの結果を知らせてくる「Webhook」の受け口がそうです。こうした URL は、確かめる対象から外します。
いちばんよいのは、そのルートを routes/web.php の外に置くことです(routes/web.php のルートに、まとめてこの確かめがかかるため)。どうしても routes/web.php に置くときは、bootstrap/app.php で外す URL を並べます。* は「ここから先は何でもよい」という意味です。
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(except: [
'stripe/*',
'http://example.com/foo/bar',
'http://example.com/foo/*',
]);
})
補足
テストを動かしているあいだは、この確かめは自動で止まります。テストのたびに合言葉を用意しなくてよいようにするためです。
JavaScript から送るとき(X-CSRF-TOKEN)#
JavaScript でデータを送るとき(Ajax)は、フォームの隠し入力欄の代わりに、リクエストの見出し(ヘッダー)の X-CSRF-TOKEN に合言葉を入れても通ります。
まず、ページの <head> に合言葉を書いておきます。
<meta name="csrf-token" content="{{ csrf_token() }}">
あとは、送るときにこの値をヘッダーに入れます。たとえば jQuery(古くからある JavaScript のライブラリ)なら、次のように書くと、すべての送信に自動で付きます。
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
Cookie を使うとき(X-XSRF-TOKEN)#
Laravel は、レスポンスを返すたびに、合言葉を暗号化して XSRF-TOKEN という Cookie(ブラウザに保存される小さなデータ)にも入れています。この値を X-XSRF-TOKEN ヘッダーに入れて送っても通ります。
Angular や Axios のような JavaScript のライブラリは、同じオリジンへ送るときに、この Cookie の値を自動で X-XSRF-TOKEN ヘッダーに入れてくれます。そのため、こうしたライブラリを使うときは、自分で何も書かなくても守られます。
関連するページ#
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。