タイトル: CSRFトークンをAjaxで使用する方法
SEOタイトル: Laravel CSRF トークンを Ajax で送信する方法|meta タグ / X-CSRF-TOKEN ヘッダ
| この記事の要点 |
|
CSRF とは
CSRF (Cross-Site Request Forgery) は悪意のあるサイトが別ドメインの認証セッションを乗っ取って勝手にリクエストを送る攻撃です。Laravel は自動で対策として CSRF トークンをセッションに紐付け、各 POST/PUT/PATCH/DELETE リクエストで検証します。
HTML フォームの場合(基本)
これで通常のフォーム送信は CSRF 検証に通ります。問題は Ajax 経由のリクエスト。
Ajax の場合: meta タグでトークンを公開
Laravel 公式推奨の方式。 内に でトークンを埋め込み、JS から読み取って送信:
{{-- resources/views/layouts/app.blade.php --}}
{{-- 他の head 要素 --}}
@yield('content')
JavaScript からの送信
jQuery
// グローバル設定: 全 Ajax リクエストに自動で付ける
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
// あとは通常通り
$.ajax({
url: '/posts',
method: 'POST',
data: { title: 'Hello' }
});
axios
// resources/js/bootstrap.js (Laravel デフォルト)
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// CSRF トークン自動付与
const token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
}
// 使う側
axios.post('/posts', { title: 'Hello' });
fetch API
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
},
body: JSON.stringify({ title: 'Hello' })
})
.then(res => res.json())
.then(data => console.log(data));
VerifyCsrfToken ミドルウェア
CSRF 検証は App\Http\Middleware\VerifyCsrfToken ミドルウェアが行います。検証されるリクエストメソッド:
- POST / PUT / PATCH / DELETE: 検証対象
- GET / HEAD / OPTIONS: 検証スキップ(仕様上、副作用なしのため)
検証順序: ① _token フィールド → ② X-CSRF-TOKEN ヘッダ → ③ X-XSRF-TOKEN ヘッダ(Cookie 由来、Sanctum SPA 用)
特定 URL を CSRF 検証から除外
外部 Webhook など、CSRF 検証ができない URL は除外:
// app/Http/Middleware/VerifyCsrfToken.php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware {
protected $except = [
'stripe/webhook/*',
'api/external/*',
];
}
419 Page Expired エラー
Ajax を送信したら 419 が返るときの原因と対処:
| 原因 | 対処 |
|---|---|
| X-CSRF-TOKEN ヘッダを付けていない | $.ajaxSetup または axios.defaults で自動付与 |
| meta タグが Blade テンプレートにない | を head に追加 |
| セッションが切れた | ログインし直す。session.lifetime を延長 |
| キャッシュされた古いトークン | ハードリロード (Ctrl + Shift + R) |
| サブドメイン跨ぎ | config('session.domain') = '.example.com' で共有 |
API ルートでは CSRF 不要
routes/api.php に書いたルートは api ミドルウェアグループに属し、CSRF 検証されません。代わりに API トークン認証を使います:
// routes/api.php (CSRF なし、Sanctum トークン認証)
Route::middleware('auth:sanctum')->post('/posts', function (Request $request) {
return Post::create($request->all());
});
// クライアント側は Bearer トークンを送る
fetch('/api/posts', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + apiToken,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
SPA との連携(Laravel Sanctum + axios)
同一ドメインの Vue / React SPA からは Sanctum の Cookie 認証 + CSRF 自動処理が便利:
// 1. /sanctum/csrf-cookie を一度叩いて XSRF-TOKEN Cookie を取得
await axios.get('/sanctum/csrf-cookie');
// 2. axios は XSRF-TOKEN Cookie を読んで X-XSRF-TOKEN ヘッダに自動付与
await axios.post('/api/login', { email, password });
// 以降のリクエストもセッション Cookie で認証される
const user = await axios.get('/api/user');
セキュリティ補足
- CSRF トークンは画面ごとに再生成されるものではない。セッションごとに 1 つ
- トークンはセッション Cookie と紐付くため、Cookie が盗まれれば CSRF 対策は意味をなさない → HTTPS 必須
- SameSite Cookie 属性(Lax/Strict)も併用すると守りが厚くなる(Laravel デフォルトで Lax)
- CSRF と XSS は別の攻撃。両方対策する必要がある