本文へ移動
Laravel Tips

CSRF 対策

ほかのサイトがログイン中の人になりすまして操作する「CSRF」という攻撃と、Laravel がそれを防ぐしくみ、フォームに書く @csrf の意味を説明します。

CSRF(シーエスアールエフ、クロスサイト・リクエスト・フォージェリ)は、悪いサイトが、あなたのアプリにログイン中の人になりすまして、勝手に操作を送りつける攻撃です。Laravel は、この攻撃を最初から防いでくれます。ふだん気をつけるのは、フォームに @csrf と1行書くことだけです。

どんな攻撃なのか#

たとえば、あなたのアプリに「メールアドレスを変える」ページがあり、/user/email へ POST(データを送る種類のリクエスト)で新しいアドレスを送ると変わるとします。

悪い人は、自分のサイトに、あなたのアプリへ向けて送信するフォームを隠して置いておきます。

blade
<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段構えで確かめます。

  1. どこから来たリクエストかを見る:いまのブラウザは、リクエストに Sec-Fetch-Site という印を自動で付けます。「自分のアプリと同じオリジン(https:// などの種類・ドメイン・ポートの組)から来た」と分かれば、そのまま通します
  2. 合言葉(CSRF トークン)を見る:1つ目で判断できないとき(古いブラウザで印が無い、HTTPS でない、など)は、合言葉が正しいかで確かめます

合言葉は「CSRF トークン」と呼ばれる長い文字列です。Laravel がセッション(同じ人のアクセスをまたいで情報を覚えておくしくみ)ごとに作り、セッションの中にしまいます。セッションを作り直すたびに変わるので、悪いサイトがこの値を知ることはできません。

いまのトークンは、次のどちらでも取り出せます。

php
use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    // リクエストのセッションから取り出す
    $token = $request->session()->token();

    // ヘルパー関数で取り出す(中身は同じ)
    $token = csrf_token();

    // ...
});

フォームには @csrf を書く#

POST・PUT・PATCH・DELETE で送るフォームを作るときは、フォームの中に @csrf と書きます。これは Blade のディレクティブ(@ で始まる命令)で、合言葉を入れた隠し入力欄に置きかわります。

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 で次のように書きます。

php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(originOnly: true);
})

このモードで確かめに失敗すると、419 ではなく 403(禁止)のエラーが返ります。

注意

Sec-Fetch-Site の印は、ブラウザが HTTPS でつないだときにしか付けません。HTTPS でないアプリでは来た場所を確かめられないので、合言葉で確かめる方法に戻ります。

サブドメイン(example.com と dashboard.example.com のような、同じサイトの別の名前)から来たリクエストも通したいときは、allowSameSite を使います。

php
->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 を並べます。* は「ここから先は何でもよい」という意味です。

php
->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> に合言葉を書いておきます。

blade
<meta name="csrf-token" content="{{ csrf_token() }}">

あとは、送るときにこの値をヘッダーに入れます。たとえば jQuery(古くからある JavaScript のライブラリ)なら、次のように書くと、すべての送信に自動で付きます。

js
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

Laravel は、レスポンスを返すたびに、合言葉を暗号化して XSRF-TOKEN という Cookie(ブラウザに保存される小さなデータ)にも入れています。この値を X-XSRF-TOKEN ヘッダーに入れて送っても通ります。

Angular や Axios のような JavaScript のライブラリは、同じオリジンへ送るときに、この Cookie の値を自動で X-XSRF-TOKEN ヘッダーに入れてくれます。そのため、こうしたライブラリを使うときは、自分で何も書かなくても守られます。

関連するページ#

公式ドキュメント(英語)

2026年10月5日時点の内容をもとに、日本語でまとめています。

ページの一覧