🐕

【Next.js/TypeScript】URL(クエリパラメータ)でユーザグループを認識させて認証情報と一緒に保持する

2022/02/26に公開

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;

参考サイト
https://maku.blog/p/r7fou3a/
https://qiita.com/Anders/items/ad91c6752c512716f82a

ローカルストレージに認証情報を保持する

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;

以下のサイトを参考にさせていただき、必要部分を肉付けした形です・・
https://marsquai.com/745ca65e-e38b-4a8e-8d59-55421be50f7e/f83dca4c-79db-4adf-b007-697c863b82a5/329eb1b4-3aff-4bb0-89dd-e1692e0aa5dc/

lscacheで有効期限付きでローカルストレージに保持

lscacheについては以下のサイトに詳しく書かれています
https://www.npmjs.com/package/lscache

残課題

セッション切れの場合にTOP画面にリダイレクトする際のグループコードをどうするかの対策がまだです。
そのうちできたら更新します。

Discussion