Laravel/PHP

[Laravel9×React]SPA認証のログイン状態管理方法について

結論

Laravel Sanctumの「sanctum/csrf-cookie」 が実行されるとCSRFトークンを`XSRF-TOKENクッキーの中に保存します。`

この「XSRF-TOKEN」が取得できるかどうかでログイン状態を判定する手法が良いと思います。

XSRF-TOKENはフロントエンドからも取得できます。Cookieへの保存なので他のサイトから窃取される心配もないです。便利な「react-cookie」というライブラリもあり読み書きも簡単、ログアウト時に削除する設計で良いと思います。

はじめに

何を書いているか

Laravel9(sanctum)でAPI側を、Reactでフロントエンドを開発するプロジェクトの場合、どのようにユーザーのログイン状態を管理するの良いのか、調査しつつ諸々試して失敗しつつ、こうするのが良いのではないかという結論が一つ出たので経緯と結論と含めてまとめます。

ソースを示して説明するような内容ではなくコラムのようなお話になると思います。

取り組む前、個人的には

「プライベートルート配下の画面については、ページ表示時に毎回ログイン状態を確認するAPIを実施して、ログイン状態でなければログイン画面にリダイレクトさせる」

でよくね、と思っていました。(しかしこれだと以下の要件を満たせませんでした。)

「マイページ」の要件

例えばログイン後に遷移できるプライベートルート配下の「マイページ」を作るとします。マイページには「マイページ」というタイトルはもちろん、ユーザーの情報やフォロー中のユーザーやお気に入りのコンテンツなどの一覧が表示されるとします。

ゲストユーザー(ログインしていないユーザー)には一瞬でも表示させたくない内容です。

このマイページに遷移時、「最初にAPIを実行」してログイン状態を取得し「コンポーネントを描画」または「ログイン画面へのリダイレクト」という要件を設定しました。

API実行とコンポーネント描画の順番のせいで要件を満たせず

結果から言うと「コンポーネントを描画」→「APIを実行(結果を得る)」となるみたいです。(描画がAPI実行結果の返却を待たない)

いやいやasync/awaitで待たせればいいじゃないか、と思うかもしれませんが丸々1日費やして私にはできませんでした。コンポーネントをreturnする処理はuseEffectを待ってくれず。最終的に`index.tsx` で呼び出すという無理やりな書き方さえ試したのに実現できずちょっと別の方法を考えよう、となりました。

Globalな状態管理

そもそもそもそも、「画面遷移のタイミングでAPIを実行する」なんていう旧来のJAMStackなWebアプリ的発想が悪いのでは無いか、もっと素晴らしくログイン状態を管理してくれる機能がReactにはあるのではないか、他の人はどのようにしているのかなどなどと思うようになりました。

Redux?Context?で管理

ここでやっとReduxというものにたどり着きました。言葉自体は知っていたのですが最近では使わないと聞いていたのでinstallしていなかったのですがものは試しに使ってみようと思いました。

いろいろ調べて後からわかったのですがReduxは、React HooksのuseContext(React16.8で追加された新機能)と同じ働きなので積極的に追加する必要はないとのこと。

故にuseContextによるGlobalな状態管理によって、ログイン状態を管理すれば解決するのではないか、と思い試してみました。

できたけど画面更新で状態がリセットされる

感動しました。ログイン前に「マイページ」に入ろうとするとログイン画面へリダイレクトされ、ログイン後は遷移できる。描画→API実行 なんてやっていた頃が懐かしくさえ感じました。

しかし、ログイン後に新しいタブで同じ「マイページ」のURLにアクセスするとどうなるんだろうと思い試してみたところ、ログイン画面にリダイレクトされまたしても膝から崩れ落ちることになりました。

GlobalなStateを永続化する

次なる目標

まあでもやりたいことは一応は実現できたので、少しずつではありますがゴールに近づいているように思えて元気が出てきました。要はこのGlobalなStateを永続化できればいいんだなという次なる目標が見えて再び調査です。

ブラウザのlocalStorageに保存するのはやめた方がいい?

結論これは絶対NOです。ブラウザのCookie以外のストレージに状態を保存するような方法は絶対採用しない方がいいと思います。他のサイトから窃取・書き換えされるリスクがあるからです。例えフラグだけの管理だとしても誰かに窃取・書き換えされる場所にGlobalなStateを保存するのは良くないかと思います。

Cookieなら安全か

Cookieなら安全ではあると思います。冒頭の結論にも書きましたが、Cookieで状態管理をする手法が結論としては良いのでは無いかと思います。

Cookieにログイン状態を保存してそれを参照することで、これまでの非JAMStackなWebアプリと近い形でログイン状態の管理ができると思います。

Cookieに何を保存する?

考えましたが、そもそもLaravel Sanctum でログイン機能を実装しているのであれば、ログイン時にCSRFトークンがCookieに保存されるようになっています。

このCSRFトークンの値を読み取ることで状態管理をするのが良いと思います。

新しくKeyを作らなくていいので手間がかかりません。

react-cookie ライブラリで読み書きを簡単に

「react-cookie」というライブラリをインストールしてみました。使い勝手はいい感じだと思います。

どこまで信頼できるDeveloperなのかは不明ですが、使ってみたところ便利だったので取り入れてみました。読み書きが簡単になり、ログイン状態をより簡単なコードで管理できます。

※ユーザーに値を書き換えられるリスク有り(問題無し)

※ユーザー側の書き換えによりログイン状態になる

Cookieはブラウザの開発者ツールから簡単に書き換えることができるしもちろん、追加することもできます。

「XSRF-TOKEN」というKeyで適当な値をCookieに追加された場合、フロントエンド側はログイン状態となってしまいます。

ただしAPIは実行できないので安全ではある

API側の設定で、重要な処理に関してはログイン後のユーザーしか実行できないようにしておけば、データが変更されたりユーザーに紐づくデータが取得される心配はないので安心かと思います。

フロント側がログイン状態なだけで、サーバー側のログイン認証に通っているわけではないのでセキュリティ面で心配することは無いです。

ただし画面の表示が崩れる

Cookieを書き換えたユーザーは画面の表示が崩れる可能性はあります。(自業自得)

ログイン後でないと表示できない画面を無理矢理表示したせいで、ログイン後でないと取得できないデータが取得できないためです。(この辺りは画面の設計によると思います。)

結論、問題なし

結論としては、書き換えを行ったユーザーの画面の表示が崩れるという状態になるだけなので大した問題はありません。Cookieの書き換えを心配する必要はないです。

結論

以上のような経緯があり、冒頭に書いた結論に至りました。

まとめると以下のようになります。

遷移毎にAPI実施 ❌描画とAPI実行(結果取得)の順番が逆になるので要件を満たさない。
Globalな状態管理 ❌ReduxやuseContextで状態を管理できるが画面更新でリセットされる。
ブラウザのローカルストレージ ❌他のサイトから窃取・書き換えのリスクがあるので採用不可
Cookieで管理 ⭕️他サイトから参照されない・状態を永続化(画面更新で消失しない)できる。
→従来の非JAMStackWebアプリに近い

結論

改めて結論です。

Laravel・React でWebアプリを開発する際のログイン状態管理方法としては、

Cookieに保存されている「XSRF-TOKEN」が参照できるかどうかで判断するのがいいと思います。

※ただしCookieの最大容量は小さいので、あれもこれもCookieに保持する設計は避けるべきかと思います。

何か間違いなどありましたらコメントを頂けますと幸いですm(_ _)m

以上です。

COMMENT

メールアドレスが公開されることはありません。