🐡

(JWT)トークン満了後、自動ログアウト機能を実装

に公開

ログイン機能ではJWTのトークン発行まで実装していましたが、トークンが切れてもログアウトされないことに気づきました。

トークンが切れるとサーバーから401エラーが返ってくるため、そのエラーを利用してログアウト処理を追加します。

JWTトークンの有効期限チェックユーティリティ関数を作る

JWTトークンには、有効期限を表す exp フィールドが含まれています。
この期限をチェックし、トークンが切れているかどうかを判定するユーティリティ関数を作ることで、クライアント側でトークンの有効性を簡単に管理できます。

// src/utills/jwt.ts
// ターミナルで設置:npm install jwt-decode 

import jwt from "jsonwebtoken";

export function isTokenExpired(token: string){
    try{
        const decodedToken = jwt.decode(token) as { exp: number };
        if (!decodedToken || !decodedToken.exp) {
            return true; // トークンの有効期限が不明な場合は、期限切れとみなす
        }
        
        const now = Date.now() / 1000; // 現在のUNIXタイムスタンプ(秒)
        return decodedToken.exp < now; // トークンの有効期限が過ぎているかどうかをチェック
    }catch(err){
        console.error("Error in isTokenExpired", err);
        return true; // エラーが発生した場合は、期限切れとみなす
    }
}

JWTトークンの有効期限チェック後、自動ログアウト処理

トークンが切れていたら、localStorage から削除し、ログインページへリダイレクトします。

// src/hooks/useAutoLogout.ts
import { useRouter } from "next/router";
import { useEffect } from "react";
import { isTokenExpired } from "../utils/jwt";

export default function useAutoLogout(token : string | null) {
    const router = useRouter();

    useEffect(()=>{
        if (!token){
            return; // トークンがない場合は何もしない
        }

        if(isTokenExpired(token)){ // トークンが期限切れの場合
            localStorage.removeItem("token");
            alert("Session expired. Please log in again.");
            router.push("/login");
        }
    },[router,token]);
    
}

グローバルページでログアウト処理を実行する

現在のグローバルページは /src/app/layout.tsx です。ここでトークンの有効期限をチェックして、自動的にログアウトさせる処理を行いたいと考えました。

しかし、/src/app/layout.tsx はサーバーコンポーネントなので、useEffectuseState などのクライアント専用フックを直接使うことができません。

そのため、クライアントコンポーネントを別途作成して、グローバルレイアウトでラップします。

AuthGuard クライアントコンポーネント

// src/components/AuthGuard.tsx
"use client";

import useAutoLogout from "@/hooks/useAutoLogout";
import { useEffect, useState } from "react";

export default function AuthGuard({ children }: { children: React.ReactNode }) {
    const [token, setToken] = useState<string | null>(null);

    useEffect(()=>{
        // ローカルストレージからトークンを取得
        const storedToken = localStorage.getItem("token")
        if (!storedToken){
            setToken(null);
            return;
        }
        // トークンが存在する場合は、stateにセット
        setToken(storedToken);
    },[])
    // トークンが変更された場合に自動ログアウトを実行
    useAutoLogout(token);

    return(
        <>{children}</>
    )
}

layout.tsx で適用例

// app/layout.tsx
import AuthGuard from "@/components/AuthGuard";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
      <html lang="en">
        <body>
          <AuthGuard>{children}</AuthGuard>
        </body>
      </html>
  );
}

Discussion