Laravel5.4から5.8へバージョンアップしたとき、APIログイン機能が動作しなくなりました。
バージョンアップに際して認証周りのコードを修正した覚えはなく、文字通り "なにもしてないのに動かない" みたいな状態になって結構焦ったので記録を残しておきます。
なおフロント側には触れず(詳しくないので)、バックエンド側の処理について書きます。
最終的に出ていたエラー
ブレークポイントを貼って一つずつ処理を追ってゆくと、最終的に "CSRF token mismatch" というエラーが出ていました。
なぜ5.4->5.8にバージョンアップしただけでこのエラーが出ているのかを探ってゆきます。
APIログイン機能(コントローラー)
まずAPIログイン機能のコードはどうなっているのかというと、以下のように記述されています。この部分はバージョンアップに際して一切手を加えていない部分です。
/**
* ログインAPI
*
* @param Request $request
* @return array|\Illuminate\Http\RedirectResponse|void
* @throws \Illuminate\Validation\ValidationException
*/
public function apiLogin(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
$request->session()->regenerate();
$this->clearLoginAttempts($request);
$this->authenticated($request, $this->guard()->user());
return [
'login'=>true,
];
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
あやしい部分
- 変更を加えていない部分の挙動が変わった
- どのタイミングで => 5.4から5.8にバージョンアップしたとき
- そのとき変更される部分は => vendor以下 => 上記のapiLogin内で使用しているメソッドが何をしているかを確認し、5.4と5.8で内容を比較する
という手順で検証を行った結果、apiLogin内の
$request->session()->regenerate();
この部分があやしいのでは??
という仮説が立ったので、内部を見てゆきます。
ここまでたどり着くまでにすでに何時間もかかってしまいました...
- 5.4↓
// Laravel 5.4
// vendor/laravel/framework/src/illuminate/Session/Store.php
/**
* Generate a new session identifier.
*
* @param bool $destroy
* @return bool
*/
public function regenerate($destroy = false)
{
return $this->migrate($destroy);
}
- 5.8↓
// Laravel 5.8
// vendor/laravel/framework/src/illuminate/Session/Store.php
/**
* Generate a new session identifier.
*
* @param bool $destroy
* @return bool
*/
public function regenerate($destroy = false)
{
return tap($this->migrate($destroy), function () {
$this->regenerateToken();
});
}
🤔
$this->regenerateToken();
😨
regenerateToken()
🤯
// Laravel 5.8
// vendor/laravel/framework/src/illuminate/Session/Store.php
/**
* Regenerate the CSRF token value.
*
* @return void
*/
public function regenerateToken()
{
$this->put('_token', Str::random(40));
}
しれっとトークンを作成しなおしていました。
解決策
APIログイン用のメソッドでreturnしているのは
return [
'login'=>true,
];
のみでした。
トークンが新しく作成されてしまうことがわかったので、そのトークンも一緒に返すようにすれば、後はフロント側でよしなにやっていただけるということだったので、最終的に
//csrf_token()でregenerateされたトークンを取得
return [
'login'=>true,
'login_token'=>csrf_token(),
];
のように変更することで事無きを得ました。