まあそんな話は置いといて、さっそく本題に移りましょう。
テーマは、React(Redux) × LaravelのSPAにおいて、ページをリロードした後もログイン状態を保持させる方法についてです。
SPAのログイン認証を作った際に発生した問題
実際に僕がReact(Redux) × Laravelでログイン認証機能を作っていたときの話です。
作った機能について簡単に説明します。
まずLaravel側でログインAPIを作り、ログイン成功時はログインしたユーザー情報を返すようにします。
React側ではログイン時にそのAPIを叩く処理と、返り値をstateに入れる処理を作ります。
ログイン状態かログアウト状態かは、このstateにユーザー情報が入っているか否かで識別するので、
つまり、ログインが成功すればstateにユーザー情報が入り、ログイン状態になるという仕組みを作りました。
これでログイン認証機能は完成したと誰もが確信していた。
しかし…
そうです。このログイン認証機能には欠陥があったんです。
ログインが成功してstateにユーザー情報が入っても、ページをリロードしたり、一度ページを離れてしまうとstateが初期化されるため、そのたびにログイン状態が消えてしまうんです。
いろいろ考えた結果、一つの解決策にたどり着きました。
結論から言うと、
リロード時にもログイン状態を更新できるAPIを叩けばいいんです。
今回は上記の実現方法を紹介します。
ログイン状態を保持させるロジック
実行環境
・Laravel 9.22.1
・Laravel Sanctum 3.0
・React 17.0.2 (TypeScript)
・Redux Tool Kit 1.8.3
LaravelでのSPA認証はLaravel Sanctumを使います。
ログイン状態のstate管理についてはRedux Tool Kitを使います。
それぞれの導入方法についてはここでは割愛します。
ロジックの説明
ロジックをざっくり説明します。
Laravel側でログインAPIの他に、ログイン中のユーザー情報をGETで取得するAPIを作ります。
React側ではuseEffectを使って、初回レンダリング時にそのAPIを叩き、stateに返り値であるユーザー情報を入れる処理を作ります。
そうすることで、リロードしても毎回ユーザー情報がstateに入るので、ログイン状態が保持されるという仕組みです。
実装してみよう
Laravel側の実装
まずはLaravel側から実装していきます。
とは言っても、今回導入しているLaravel Sanctumには、ログイン中のユーザー情報を取得するAPIがデフォルトで実装されています。
routes/api.phpを見てみましょう。
// api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
このAPIを叩けば、ログイン中のユーザー情報が取得できます。
また、このAPIはSanctumの認証機能により、ログイン中にしか叩けない状態となっています。
Sanctumはログイン成功時にセッションIDを生成してくれるのですが、そのセッションIDからログイン状態を識別し、認証を行ってくれます。
なのでログアウト中にこのAPIを叩いてもユーザー情報は返ってこず、フロント側でもstateにユーザー情報が入らないので、ログイン状態にもならないというわけです。
詳しい仕組みは公式ドキュメントをご参照ください。
React(Redux)側の実装
次にReact側の実装です。
初回レンダリング時にLaravelで作ったAPIを叩く処理を実装します。
今回Reduxを使っているので、ログイン状態のstateはstoreで管理するとして、Redux Tool KitのcreateAsyncThunkを使ってAPIを叩く関数を作ります。
action.payloadでAPIの返り値(ユーザー情報)を受け取り、stateに入れます。
コードは以下の通りです。(実際はログイン、ログアウト処理もこのAuthSlice.tsに記述していますが、今回は省略します)
// AuthSlice.ts
import { AuthUserType } from "../../types/authType";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
import { RootState } from "../../app/store";
// stateの初期値
const initialState: AuthUserType = {
user: undefined,
isLoading: false,
error: undefined,
};
// ログイン中のユーザー情報を取得するAPIを叩く関数
export const fetchAuthUser = createAsyncThunk(
"auth/fetchAuthUser",
async () => {
const response = await axios.get(`/api/user`);
return response.data;
}
);
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchAuthUser.pending, (state) => {
state.isLoading = true;
state.error = undefined;
})
.addCase(fetchAuthUser.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload; // fetchAuthUserが実行され、返り値がstateに入る
state.error = undefined;
})
.addCase(fetchAuthUser.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message;
});
},
});
export const selectAuth = (state: RootState) => state.auth;
export default authSlice.reducer;
fetchAuthUser
をuseEffectで初回レンダリング時にdispatchします。
// App.tsx
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(fetchAuthUser());
}, []);
これで実装は完了です。
終わりに
React(Redux) × Laravelという少し特殊な環境のSPAでログイン状態を保持させる方法を紹介しました。
実装自体はとても簡単なのでぜひ参考にしていただければと思います。
ロジカルスタジオではPHPエンジニアを募集しています。
今回使用したLaravelは特によく使われる技術なので、ご興味のある方は是非以下よりご応募ください!