はじめに
こんにちは、RECJob開発チームエンジニアの佐藤(@r_sato1201)です。
RECJobではLaravelを採用しており、認証システムにLaravel SanctumのSPA認証を使用しています。
今回は、Sanctumを使用した際にXSRF-TOKENクッキーの取り扱いでハマったので備忘録も兼ねて書きたいと思います。
Laravel SanctumのSPA認証の流れ
まず、Laravel SanctumのSPA認証の流れを書きたいと思います。
(仕組みや詳しいことは公式リファレンスのSanctumのページを参照してください。)
- /sanctum/csrf-cookieにリクエストしCSRF保護を初期化
- CSRFトークンを含むXSRF-TOKENクッキーを発行してセット(以降はX-XSRF-TOKENヘッダにトークンを入れてリクエストする)
- ログインする
- セッションを保持し、セッションクッキーを発行
- Sanctumのミドルウェアを設定している何らかのルーティングにリクエスト
- セッションクッキー内で自動的に認証
- 認証成功の場合は継続、失敗の場合は401 or 419を返す
ざっくりとですが大体こんな感じです。
何が起きたか
今回起きた事象ですが、本番環境(hoge.jp)にログイン後、ステージング環境(xxx.hoge.jp)でログインする際に
上の流れの2で発行したXSRF-TOKENのキー名が競合し419(csrf token mismatch)が起きるようになってしまいました。
どう解決するか
本番環境とステージング環境とでXSRF-TOKENクッキーのキー名を変更し、それを使えるようにします。
まず、XSRF-TOKENクッキーを生成する際に各環境のprefixをつけるようにします。
例)
本番環境:PRODUCTION-XSRF-TOKEN
ステージング環境:STAGING-XSRF-TOKEN
具体的には、VerifyCsrfTokenミドルウェアの新しいクッキーを生成する箇所をオーバーライドし、各環境のprefixをつけたキー名にするようにします。
app/Http/Middleware/VerifyCsrfToken.php
<?php namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; use Symfony\Component\HttpFoundation\Cookie; class VerifyCsrfToken extends Middleware { /** * The URIs that should be excluded from CSRF verification. * * @var array<int, string> */ protected $except = [ // ]; /** * Create a new "XSRF-TOKEN" cookie that contains the CSRF token. * * @param \Illuminate\Http\Request $request * @param array $config * @return \Symfony\Component\HttpFoundation\Cookie */ protected function newCookie($request, $config) { return new Cookie( strtoupper(config('app.env')) . '-XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']), $config['path'], $config['domain'], $config['secure'], false, false, $config['same_site'] ?? null ); } }
次に、フロント側でキー名を変えたXSRF-TOKENを受け取れるようにします。
キー名を変更していなければAxiosなどのHTTP Clientライブラリであれば、特に設定しないでもこのあたりは自動でやってくれますが
今回は変更したキー名で受け取れるようaxiosのInstance生成時に設定します。
api/axiosInstance.ts
import axios from 'axios' const APP_ENV = process.env.NEXT_PUBLIC_APP_ENV ?? '' export const axiosInstance = axios.create({ withCredentials: true, xsrfCookieName: APP_ENV.toUpperCase() + '-XSRF-TOKEN', })
これらを行うだけで、XSRF-TOKENのキー名の競合を解決しそれぞれの環境で扱うことができるようになります。
最後に
同じようなことにハマった人の手助けとなればと思い書きました。
今後も業務の中で発見した知見があったら書いていきたいと思います。
現在株式会社ROXXは一緒にはたらく仲間を募集中です。
herp.careers