LoginSignup
12
10

getServerSidePropsを用いて、ログインの有無を判断しリダイレクトを実装する

Last updated at Posted at 2023-06-12

はじめに

Next.js初心者なりに、getServerSidePropsを用いてページアクセス時のリダイレクトを実装してみました。

要件

  • ログインしていない場合、認証が必要なページにアクセスした際にログイン画面にリダイレクトする
  • 逆に、ログイン済みの場合、ログイン画面を表示しようとした時ホーム画面にリダイレクトする
  • ユーザー情報の保持はRecoilを使用。recoil-persistなどのライブラリの使用はなし
  • 認証はアクセストークン認証で、トークンはCookieに保存
  • トークンの有効性はAPIを叩いて正常なレスポンスが返ってくるかで判断(APIは既製のものを使用した)
    • /accountにリクエストを送ると、tokenが有効であれば以下のようなデータが返ってくる

      {
        "user": {
          "id": 0,
          "name": "string",
          "email": "string",
          "iconImageUrl": "string"
        }
      }
      
    • tokenが無効なら以下のように返ってくる(空データなだけであって、エラーにならない点に注意)

      {}
      

実装

以下、実装例です。getServerSidePropsの関数は、redirect.tsにまとめて定義し、各ページコンポーネントでredirectToLogin関数とredirectToHomepage関数の必要な方をインポートして使うことを想定しています。

utils/redirect.ts

import { GetServerSideProps } from "next";
import axiosInstance from "./axios";

// 空のオブジェクトかどうかを判定する関数
export const isEmptyObject = (obj: object) => {
  return Object.keys(obj).length === 0;
};

// ログインページへのリダイレクト関数
export const redirectToLogin: GetServerSideProps = async (context) => {
  const token = context.req.cookies.token;

  // そもそもトークンがなければ即リダイレクト
  if (!token) {
    return {
      redirect: {
        destination: "/login",
        permanent: false,
      },
    };
  }

  try {
    // リクエストヘッダーにトークンを添付してリクエストを送信(js-cookieライブラリはサーバーサイドで使えないので、
    // axiosInstanceで自動でtoken付与されない。そのため、ここでtokenを付与している)
    const response = await axiosInstance.get("/account", {
      headers: { Authorization: `Bearer ${token}` },
    });

    // getAccountはTokenを設定せずにリクエストを送った場合、200レスポンスで{}が返ってくる。なので以下のような判定をする。
    // レスポンスが空のオブジェクトなら(トークン無効)、ログインページへリダイレクト
    if (isEmptyObject(response.data)) {
      return {
        redirect: {
          destination: "/login",
          permanent: false,
        },
      };
    }

    // トークンが有効ならユーザーデータをpropsとして渡す
    // これにより、リロード時にrecoilのユーザーデータが消えることを防ぐ
    return {
      props: { user: response.data.user },
    };
  } catch (error) {
    console.error(error);
  }

  return {
    props: {},
  };
};

// ホームページへのリダイレクト関数
export const redirectToHomepage: GetServerSideProps = async (context) => {
  const token = context.req.cookies.token;

  try {
    const response = await axiosInstance.get("/account", {
      headers: { Authorization: `Bearer ${token}` },
    });

    // レスポンスが空のオブジェクトでなければ(トークン有効)、ホームページへリダイレクト
    if (isEmptyObject(response.data) === false) {
      return {
        redirect: {
          destination: "/homepage",
          permanent: false,
        },
      };
    }
  } catch (error) {
    console.error(error);
  }

  return {
    props: {},
  };
};

具体的にリダイレクトをしているのは以下の部分

      return {
        redirect: {
          destination: "/login",
          permanent: false,
        },
      };

permanent オプションは、永続的なリダイレクトかどうかを定義する。ページ移転やドメイン引越などでなければ基本はfalseで問題ない。

ページコンポーネント

// homepage.tsx
import React, { useEffect } from "react";
import Header from "@/components/Header";
import { useRecoilState } from "recoil";
import { userState } from "@/recoil/atoms";
import { TOKEN_KEY } from "@/utils/cookieConstants";
import Cookie from "js-cookie";
import { redirectToLogin } from "@/utils/redirect";
import axiosInstance from "@/utils/axios";
import { User } from "@/types";

interface HomePageProps {
  user: User;
}

// getServerSiderPropsから渡されたユーザーデータをPropsとして受け取る
const HomePage: React.FC<HomePageProps> = ({ user: initialUser }) => {
  const [user, setUser] = useRecoilState(userState);
  const token = Cookie.get(TOKEN_KEY);

  // コンポーネントがマウントされた時に、getServerSidePropsからユーザーデータをセットする
  // これは、リロード時にユーザーデータがRecoilから消えてしまうのを防ぐため
  useEffect(() => {
    setUser(initialUser);
  }, [initialUser, setUser]);

  return (
    <>

	〜省略〜

    </>
  );
};

export default HomePage;

// 以下でリダイレクト処理を呼び出す
export const getServerSideProps = redirectToLogin;

export const getServerSideProps = redirectToLogin; でリダイレクト処理を呼び出している。

getServerSidePropsからは、Propsでユーザーデータも受け取って、useEffectでRecoilのstateにセットしている。

Recoilはrecoil-persistなどのライブラリを使うか、ローカルストレージを使わないとリロード時の状態の永続化ができないため、このような処理にした。

逆に、ログインページではredirectToHomepageを呼び出す。

// login.tex
 省略 

export default Login;

export const getServerSideProps = redirectToHomepage;

嵌った点

getServerSideProps内では、js-cookieでCookieを参照できない。

どう困ったのかというと、axiosInstance内で自動でCookieからtokenを取得してヘッダーに付与しようとしていたのが、うまく機能しなかったこと。

import axios from "axios";
import Cookies from "js-cookie";
import { TOKEN_KEY } from "./cookieConstants";

const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_DOMAIN,
  timeout: 1000,
});

// リクエストを行う直前に毎回Authorizationヘッダーを付与する
axiosInstance.interceptors.request.use(
  function (config) {
    const token = Cookies.get(TOKEN_KEY); // トークンを取得(この部分がgetServerSideProps内だとうまく動かない!!)

    // トークンがあればリクエストヘッダーに添付
    if (token) {
      config.headers["Authorization"] = "Bearer " + token;
    }

    return config;
  },
  function (error) {
    // リクエストエラー時の処理
    return Promise.reject(error);
  }
);

export default axiosInstance;

それに気づかず、無駄に時間を溶かした…

解決策として、getServerSideProps内で、contextを通してtokenを取得し、以下のようにヘッダーにtokenを付与している。

    export const redirectToLogin: GetServerSideProps = async (context) => {
    const token = context.req.cookies.token;		

    // リクエストヘッダーにトークンを添付してリクエストを送信(js-cookieライブラリはサーバーサイドで使えないので、
    // axiosInstanceで自動でtoken付与されない。そのため、ここでtokenを付与している)
    const response = await axiosInstance.get("/account", {
      headers: { Authorization: `Bearer ${token}` },
    });

~ 省略 〜
}
12
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10