🐕
【Next.js/TypeScript】URL(クエリパラメータ)でユーザグループを認識させて認証情報と一緒に保持する
Next.js初心者なので、調べながら実装したことを自分なりにざっくりメモしておきます。
やりたいこと
TOP画面のURLにユーザグループのクエリパラメータを持たせ、ログイン認証と一緒に保持させる
ざっくりなイメージは以下
A、Bグループは同じアプリを使う
各テーブルにグループコードをもたせていて、各グループは他グループのデータを意識せずにアプリを利用する
データ参照時、更新時にグループコードを利用する
- Aグループ(groupCode=11111)
- ユーザX → ログインしたら、Aグループの情報のみ参照・更新できる
- ユーザY → 〃
- Bグループ(groupCode=22222)
- ユーザZ → ログインしたら、Bグループの情報のみ参照・更新できる
TOP画面のURL例
画面イメージ
ざっくり実装方法
- TOP画面のURLにグループコードのクエリパラメータをもたせる
- トップ画面のFormのコンポーネントにて、クエリパラメータのグループコードを取得
- APIにてグループ情報を取得
- TOP(ログイン)画面に、グループ名を表示
※クエリパラメータに設定しているグループコードがテーブルに存在していなかったら、「URLが間違っています」と表示 - ログイン時に、ログインユーザ情報と一緒に、ローカルストレージへ登録
- ログアウト時に、クエリパラメータにグループコードをつけてTOP(ログイン)画面に戻る
ざっくりコード
components/LoginForm.tsx
// クエリパラメータの取得にはuseRouterを利用
import { useRouter } from "next/router";
export const LoginForm = () => {
// クエリパラメータに設定しているグループコードを取得
const router = useRouter();
const { groupCode } = router.query;
// ローカルストレージに認証情報などをセットするContext
const [authInfo, setAuthInfo] = useContext(AuthInfoContext);
// 取得したグループ情報をセットするstate
const [groupData, setGroupData] =
useState<GroupTableData>({ data: [] });
// TOP画面に表示するグループ名・エラーメッセージをセットするstate
const [groupName, setGroupName] = useState("");
useEffect(() => {
const getGroupData = async () => {
// グループ情報を取得するAPI(別途定義 ここでは割愛)
const result = await postGroupCode(String(groupCode));
if (result.message === "no data") {
setGroupName("--- URLが間違っています ---");
} else {
setGroupName(result.data[0].group_name);
setGroupData(result);
}
};
// routerから情報が完全に取得できるかどうか確認
if (router.isReady) {
getGroupData();
}
}, [groupCode]);
~~~ 省略
return (
<form onSubmit={handleSubmit(signInUser)}>
// ログイン画面にグループ名を表示
<h3>
<span style={{ color: "blue" }}>{groupName}</span>
</h3>
~~~ 省略
Next.jsでのクエリパラメータの取得方法
useRouterフックを使うといいらしい
import { useRouter } from "next/router";
export const LoginForm = () => {
const router = useRouter();
const { groupCode } = router.query;
参考サイト
ローカルストレージに認証情報を保持する
useContextを利用
context/AuthContext.tsx
import React, { useEffect, useState } from "react";
import lscache from "lscache";
import { useRouter } from "next/router";
// グループ情報の型定義
import { GroupTableDataSet } from "../types/WebData";
/**
* 認証情報の型
*/
type AuthInfo = {
userCode: string;
name: string;
authority: string;
group: GroupTableDataSet;
};
// 認証情報の初期値定義
const initialAuthInfo = {
userCode: "",
name: "",
authority: "",
group: {
code: "",
group_name: "",
},
};
// ログイン状態のContext
export const LoggedInContext = React.createContext<boolean>(false);
// 認証情報と認証情報セットのContext
export const AuthInfoContext = React.createContext<
[
AuthInfo,
React.Dispatch<React.SetStateAction<AuthInfo>>,
React.Dispatch<React.SetStateAction<boolean>>
]
>([initialAuthInfo, () => {}, () => {}]);
/**
* デフォルトのAuthInfoを取得
* ローカルストレージから取得できた場合はその値をパース
* 取得できない場合は空の情報を返す
* @returns
*/
function getDefaultAuthInfo(): AuthInfo {
if (typeof window !== "undefined") {
const defaultAuthInfo = lscache.get("authInfo");
if (defaultAuthInfo) {
return defaultAuthInfo as AuthInfo;
} else {
return initialAuthInfo;
}
}
return initialAuthInfo;
}
/**
* 認証情報をローカルストレージに追加
* @param authInfo
*/
function setAutoInfoToLocalStorage(authInfo: AuthInfo): void {
// localStorageの有効期限10分に設定
lscache.set("authInfo", authInfo, 10);
}
export const AuthContextProvider: React.FC<{}> = (props) => {
const router = useRouter();
// login状態、認証情報のstateの定義
const [loggedIn, setLoggedIn] = useState<boolean>(false);
const [authInfo, setAuthInfo] = useState<AuthInfo>(getDefaultAuthInfo());
// authInfoのバリデーション
useEffect(() => {
// authInfoに正しく値がセットされているかどうかをチェック
if (authInfo?.userCode !== "") {
// 認証情報をローカルストレージにセットし、ログイン状態ONにする
setAutoInfoToLocalStorage(authInfo);
setLoggedIn(true);
} else {
if (authInfo?.group.code !== "") {
// ログアウト実行の場合 :ローカルストレージの認証情報を削除し、ログイン状態をOFFにする
const groupCode = authInfo.group.code;
lscache.remove("authInfo");
setLoggedIn(false);
// クエリパラメータにグループコードを付けたTOP画面に戻る
router.push(`/?group=${groupCode}`);
}
}
}, [authInfo]);
return (
<LoggedInContext.Provider value={loggedIn}>
<AuthInfoContext.Provider value={[authInfo, setAuthInfo, setLoggedIn]}>
{props.children}
</AuthInfoContext.Provider>
</LoggedInContext.Provider>
);
};
_app.tsxに設定
_app.tsx
~~~ (略)~~~
import { AuthContextProvider } from "../context/AuthContext";
import lscache from "lscache";
~~~ (略)~~~
function MyApp({
Component,
pageProps: { session, ...pageProps },
router,
}: AppPropsWithLayout) {
useEffect(() => {
// ここに全ページ共通で行う処理
// ログインページへのアクセスの場合は処理をしない
if (router.pathname === "/") return;
// ログインユーザID期限切れのデータを削除
lscache.flushExpired();
// ログインユーザIDが取得できない場合、ログインページへリダイレクト
if (!lscache.get("authInfo")) {
router.push("/");
}
}, [router.pathname]);
return (
<AuthContextProvider>
<Component {...pageProps} />
</AuthContextProvider>
);
}
export default MyApp;
以下のサイトを参考にさせていただき、必要部分を肉付けした形です・・
lscacheで有効期限付きでローカルストレージに保持
lscacheについては以下のサイトに詳しく書かれています
残課題
セッション切れの場合にTOP画面にリダイレクトする際のグループコードをどうするかの対策がまだです。
そのうちできたら更新します。
Discussion