環境
- Laravel11 + Breeze + Inertia.js + React でユーザーログイン周りのScaffoldを作った。
- 操作は全てReactからのAPIコール
- AuthはStatefulAPIを使って、Sanctumで行う。Routeにミドルウェアを
Route::middleware('auth:sanctum')
って感じ。 - ログイン後にTokenを取得してCOOKIEにセットしている。
useEffect(() => { (async () => { await fetch(route('sanctum.csrf-cookie')) })(); }, []);
const [cookies, setCookie, removeCookie] = useCookies(['XSRF-TOKEN']); axios.interceptors.request.use(config => { config.headers!['X-XSRF-TOKEN'] = cookies['XSRF-TOKEN'] return config; }, error => { return Promise.reject(error) })
再現方法
- ログインする。
- sanctumで保護されているAPIを叩くページに移動しても問題ない。
- プロフィールページ
http://localhost:8000/profile
からパスワードを変更 - sanctumで保護されているAPIを叩くページに移動すると、401 unauthorizedを返される。
理由
セッションTokenとブラウザから送られるTokenが一致していない。
tokensMatchメソッドでセッションToken$request->session()->token()
とRequestからのToken $this->getTokenFromRequest($request);
が違うのでfalseを返す。
<?php protected function tokensMatch($request) { $token = $this->getTokenFromRequest($request); return is_string($request->session()->token()) && is_string($token) && hash_equals($request->session()->token(), $token); }
セッション側がプロフィールページでパスワードを変更した際に更新されているのにRequestトークンがそれに追いついていない。ちなみに、Requestトークンは X-CSRF-TOKEN
または X-XSRF-TOKEN
ヘッダを見ている。
理由その2
なぜ一致しないかというと、PasswordControllerが成功時に back()
をレスポンスしている。…からじゃないかな?
<?php class PasswordController extends Controller { /** * Update the user's password. */ public function update(Request $request): RedirectResponse { $validated = $request->validate([ 'current_password' => ['required', 'current_password'], 'password' => ['required', Password::defaults(), 'confirmed'], ]); $request->user()->update([ 'password' => Hash::make($validated['password']), ]); return back(); } }
解決方法(失敗)
- AjaxでAPI的に使っているので、back()を使わないようにする。
- Jsonを返して処理するだけにしたいけど、
resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx
は ControllerがInertia::render()
を返してくるのを期待しているので使わないようにする。自前でaxiosで処理する。
…と思ってたら、やっぱり駄目だった。
どうも、passwordにUPDATEをかけると何かLaravelがセッションかTokenの処理をするっぽい。nameの更新だけだと問題なかった。
ということで、Passwordまわりで何か特別な処理をしていないかを探る。
src/vendor/laravel/framework/src/Illuminate/Session/Middleware/AuthenticateSession.phpの public function handle($request, Closure $next)
なんかこのあたりでやってそうだなー。
もう面倒だから、「パスワード変更したら再ログイン必要」ってことにしちゃおう。