【Laravel×Vue.js】CSRF対策をVue側に記述する方法!POST送信が419になる理由
この記事からわかること
- Laravel×Vue.jsを使ったSPAでのPOST送信方法
- 419になってしまった理由と対策
- Vue.js側にformを実装する方法
- Laravelの@csrfを設置するには?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
PHPのフレームワークであるLaravelとJavaScriptのフレームワークVue.jsを使ってSPA(Single page application)を作っている時にformから値を送信時に419ページになった時の原因や解決法、CSRF対策を施す方法をまとめました。
実現したいことと環境
- Laravel×Vue.jsでのSPA制作
- form要素をVue.js(.vueファイル)側に実装
- Post通信すると419ページになるのを解決
- CSRF対策を施したい
CSRFとは?
そもそも「CSRF」(Cross-Site Request Forgeries:クロスサイトリクエストフォージェリ)とは、URLをクリック時にそこから悪意を込めたWebサービスへのリクエストが送信されてしまうのが特徴のサイバー攻撃の1つです。
例えばURLをクリックしただけなのに勝手に記事を更新されたり、削除されたり、公開されたりといったPOST送信で操作するようなことを予期せぬデータや手順で操作されてしまうのです。
CSRFの対策として有効なのがPOST送信時のパラメータにトークンを含め、受け取り側のURLでもトークンを発行し、そこに差異がない場合のみ許可するといった「ワンタイムトークン」と呼ばれる対策方法です。
LaravelのCSRF対策
Laravelに常設されているCSRF対策がまさしくこの「ワンタイムトークン」を使用しています。
使用方法は簡単でbladeテンプレート内(XXXX.blade.phpファイル)に設置しているform要素の中に「@csrf」というbladeディレクティブを入れこむだけです。
<form method="POST" action="URL">
@csrf
<label>メールアドレス</label>
<input name="email" type="text">
<label>タイトル</label>
<input name="title" type="text">
<label>お問い合わせ内容</label>
<textarea name="body"></textarea>
<button type="submit">
入力内容確認
</button>
</form>
@csrfディレクティブを入れ込むことでform内に非表示にされたinput要素が作られ、その値にトークンが埋め込まれています。Laravelではそのトークンが正しいものだけを受け付けるようになっているので逆に@csrfを記述しないと受け付けられずエラーが起きてしまいます。
と言ってもページごとにCSRF対策の有効/無効は切り替えることができるので必要なければ無効にすれば@csrfの記述がなくてもエラーが起きないようにすることもできます。
Vue.js内に@csrfを組み込む方法
結論から言うとVue.js内にはLaravelのblade構文(ディレクティブなど)は組み込むことができません。@csrfが使用できるのはbladeテンプレート(XXXX.balde.php)の中だけなのでVue.js(XXXX.vue)のテンプレートを使っている場合は組み込むことができないのです。
なのでVue.js内にformを設置してPOST送信使用とすると以下のように419ページ(有効期限切れ)が表示されてしまいます。
これを防ぐ方法は2つ
- 別の方法でCSRF対策を行う
- LaravelのCSRF対策を不可にする
Vue.jsで行うCSRF対策
参考記事:Laravel公式マニュアル
Vue.jsで行うCSRF対策を実現する手順
- head内にトークンメタタグを配置
- Vue.jsのテンプレート内にinput要素を配置
- javascriptでメタタグ内のトークンを取得しinput要素の値に充てがう
Vue.js内でCSRF対策を行うにはまずHTMLのhead要素の中に以下のコードを追加します。これでメタタグに一意のトークンを追加することができます。
<meta name="csrf-token" content="{{ csrf_token() }}">
続いてVue.jsのテンプレート内に設置しているform要素の中に以下のコードを追加します。今回はVuexを使っているので$store.state.csrf
としていますが、Vuexを使っていない場合は適当な算出プロパティ(computed)を定義してあげてください。
この値(value)部分にjavascriptコードでメタタグのcontent部分を取得し、変数に格納したものを入れ込みます。
<input type="hidden" name="_token" :value="$store.state.csrf">
Vue.jsテンプレートに組み込んだ場合
<template>
<form method="POST" action="URL">
<!-- @csrfディレクティブは使えない -->
<!-- CSRF対策用の非表示input要素を定義、値はv-bindして変数を充てがう -->
<input type="hidden" name="_token" :value="$store.state.csrf">
<label>メールアドレス</label>
<input name="email" type="text">
<label>タイトル</label>
<input name="title" type="text">
<label>お問い合わせ内容</label>
<textarea name="body"></textarea>
<button type="submit">
入力内容確認
</button>
</form>
</template>
<script>
export default {
name: 'csrf-form',
props: {
},
computed: {
csrf() {
// Vuex未使用ならこのように定義
},
}
};
</script>
メタタグのトークンを取得するのは以下のコードです。説明するまでもないですが、meta[name="csrf-token"]
要素のcontent属性
の値を取得しています。これを変数に格納して先ほどの値に渡せば完了です。
document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
Vue.use(Vuex);
export default new Vuex.Store({
strict: true,
state: {
// csrf対策
csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
},
これで@csrfを記述しなくてもVue.jsで記述したHTMLからのPOST送信で419ページが表示されることなく正常にアクセスすることができます。
LaravelのCSRF対策を不可にする
この方法はCSRF対策を行わないことになるのでformでの送信データをデータベースに保存しないなど重要ではない場合に使える方法です。
LaravelのCSRF対策を行なっている心臓部分は「Http」>「middleware」>「VerifyCsrfToken.php」の中に記述されています。
├── Laravelプロジェクト
│ ├── artisan
│ └── app
│ ├── Console
│ ├── Exception
│ └── Http
│ ├── Controllers
│ ├── Kernel.php
│ └── Middleware
│ └── VerifyCsrfToken.php
その中の$except
と言う変数部分がCSRF対策を無効にする配列となっています。
protected $except = [
// 例
'home',
'home/*',
]
この配列に無効にしたいアクション(action)先のURLを記述することで指定のactionへのCSRF対策によるワンタイムトークンが無効になり、@csrfを記述しなくても419ページが表示されずにアクセスすることができるようになります。配列になっているので指定したいURLを複数記述することもできますし、ワイルドカード(*)を使って配下全てに適応させることもできます。
またアプリ全体で無効にするには「Http」>「Kernel.php」の中の$middlewareGroups
配列内の以下の文をコメントアウト(削除)すれば無効にすることができます。
\App\Http\Middleware\VerifyCsrfToken::class,
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。