LoginSignup
52
43

More than 3 years have passed since last update.

初心者の私がLaravel+Vue.jsでSPAを作るときのCSRFトークンでつまずいたこと

Posted at

はじめに

Laravel+Vue.jsでSPAを作るときのCSRFトークンの扱いについてつまずいたので設定方法や原因を私なりにまとめました。
もっと良い方法がある、この方法だとこれがだめ、といったことがありましたら指摘いただければ幸いです。

環境&前提

  • Laravel 5.8
  • 認証機能はLaravel標準を使用
  • Vue.js 2.6.10
  • axiosでAPIを叩いてPOST

設定方法

結論から言うと、今回の私の環境や前提では特に何もしなくてよいでした。

前提通りなら特に設定は不要で、axiosがCookieからCSRFトークン(を暗号化したもののようです)を取得して送信してくれます。

日本語のドキュメントにも書いてありますね…
https://readouble.com/laravel/5.8/ja/csrf.html#csrf-x-xsrf-token

いくつかのJavaScriptフレームワークや、AngularとAxiosのようなライブラリーでは、自動的に値をX-XSRF-TOKENヘッダに設定するため、利便性を主な目的としてこのクッキーを送ります。

私はこれを知らず、余計なことをしたためにハマりました。。。

私がハマったこと

状況

ログイン直後にフォームからPOSTすると、CSRFトークン不一致のエラーになる。
画面をリロードすると正常にPOSTできる。

やったこと(誤っていた設定)

https://readouble.com/laravel/5.8/ja/csrf.html#csrf-x-csrf-token
上記を参照して以下2点を行いました。

  1. headタグ内にCSRFトークンを埋め込む
    <meta name="csrf-token" content="{{ csrf_token() }}">

  2. resources/js/bootstrap.js を読み込む
    このファイルに以下のような記載があって、これでmetaタグのトークンをaxiosのヘッダに埋め込んでいるようです。

resources/js/bootstrap.js
let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

トークン不一致の原因

フロント側のCSRFトークンが更新されず、サーバ側が持っている値と一致していないためでした。

それぞれ、以下のような状態です。
- フロント側:ログインしてもHTMLが再読み込みされるわけではないので、headタグ内のCSRFトークンの値は更新されない
- サーバ側:ログインによってトークンが更新される
※Laravel標準の認証機能だと、サーバ側のトークンがログインのタイミングで更新されるようになっていました

何もしないでもトークンを送ってくれるんじゃないの?

となるんですが、実際axiosはCookieから取得したトークンを送ってくれていました。
ただ、Laravel側で参照するトークンの優先順位があり、headタグ内に埋め込んだほうの更新されてないトークンが参照されていたためにエラーとなっていたようです。

LaravelのCSRFトークンの参照の優先順位

優先順位はこうなっています。
 高:HTMLのheadタグに埋め込まれたX-CSRF-TOKENの値
 低:Cookieから取得したX-XSRF-TOKENの値(axiosが自動で送信してくれる値)
なので、どちらも送られている場合はX-CSRF-TOKENの値が参照され、不一致エラーとなっていました。

こちらのブログの図が非常にわかりやすかったです。ありがとうございます。
http://shirangana.omaww.net/laravel52/laravel%205.2%20csrf%20token%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF

なお、ドキュメントでこの優先順位について記載がなかったので、VerifyCsrfTokenクラスを確認したところそれらしいメソッドがありました。

VerifyCsrfToken.php
protected function getTokenFromRequest($request)
    {
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

        if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
            $token = $this->encrypter->decrypt($header, static::serialized());
        }

        return $token;
    }

そもそも環境によって使い分けるべきなので優先順位とかは本来あまり関係ないのだろうなと思いました。
(更に言うと初めからソースを参照していればそんなに悩まずに済んだかも…!)

52
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52
43