はじめに
Laravelで開発をしていると、Bladeテンプレートでは@csrf
や{{ csrf_token() }}
を埋め込むのが当たり前ですが、Vueフロントエンドで実装した時に「そういえばCSRF対策はどうなっている?」となったので調べて整理した備忘録です。
CSRFとは?
CSRF(Cross-Site Request Forgery) は、ユーザーの権限を悪用して「意図しないリクエスト」を送らせる攻撃です。
前提条件
- ユーザーがすでにログインしてセッションが有効
- 攻撃者が罠ページやメールを用意
- ユーザーが罠ページを踏むと、勝手にリクエストが送信される
具体例:銀行の送金操作
正常なリクエスト
POST https://bank.example.com/transfer
Content-Type: appication/x-www-form-urlencoded
to=99999999&amount=10000
攻撃者の罠ページ
<!-- GETで勝手に送金 -->
<img src="https://bank.example.com/transfer?to=99999999&amount=10000" />
<!-- POSTで勝手に送金 -->
<form action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="to" value="99999999">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>
ユーザーがログイン中であれば、本人が送金操作をしていなくても処理が走ってしまいます。
対策:CSRFトークン
そこで必要なのがCSRFトークン。
- 各リクエストごとにユーザー専用のランダムなトークンを付与
- サーバー側で一致するか検証
- 一致しなければ拒否
LaravelではBladeで次のように自動埋め込みされます。
<input type="hidden" name="_token" value="{{ csrf_tokent() }}">
サーバー側でセッションと照合し、合わなければ419 CSRF token mismatch
を返します。
攻撃者が偽造した場合は?
攻撃者がフォームに{{ csrf_token() }}
を書いても無意味です。
理由:
-
{{ csrf_token() }}
はLaravelがログインユーザーごとに生成する一意な値 - 攻撃者のHTMLでは単なる文字列
"{{ csrf_tokne() }}"
- サーバーで比較すると不一致→処理失敗
Vue + Laravel(Inertia)でのCSRF対策
Bladeを使わないVue構成でも、LaravelのVerifyCsrfToken
ミドルウェアが働いているので、以下の設定をすればOKです。
-
app.blade.php
にCSRFトークンを埋め込む
<meta name="csrf-token" content="{{ csrf_token() }}">
-
axios
で自動的に送信するよう設定
resources/js/bootstrap.js
などで次を追加:
import axios from 'axios';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found');
}
これでVueからのフォーム送信やAPI呼び出し時にもCSRFトークンが自動で付与され、Laravel側で検証されます。
まとめ
- CSRF攻撃は「ユーザーのログイン状態を悪用して不正リクエストを送る」手口
- Laravel(Blade)では
@csrf
を使えば自動で対策できる - Vue + Inertia環境でも
metaタグ + axios設定
で問題なくCSRF保護が効く - トークンが盗まれない限り攻撃は成立しない
- 逆にXSSで盗まれると危険なので、XSS対策も重要