認証状態の判定
フック
src/frontend/src/hooks/useAuth.ts
"use client";
import { useEffect, useState } from "react";
/**
* JWT の有効期限を監視し、認証状態を返すカスタムフック。
*
* - ローカルストレージの access トークンを読み取り、exp クレームを検証する。
* - 有効期限が切れたタイミングで自動的にトークンを削除し、未認証状態へ遷移する。
* - isLoading が true の間は判定中なので、UI はローディング表示にすること。
*/
const useAuth = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (typeof window === "undefined") return;
const access = localStorage.getItem("access");
if (!access) {
setIsAuthenticated(false);
setIsLoading(false);
return;
}
const clearAuth = () => {
localStorage.removeItem("access");
localStorage.removeItem("refresh");
setIsAuthenticated(false);
};
let timer: ReturnType<typeof setTimeout> | undefined;
try {
const payload = JSON.parse(atob(access.split(".")[1]));
const msUntilExpiry = payload.exp * 1000 - Date.now();
if (msUntilExpiry <= 0) {
clearAuth();
setIsLoading(false);
return;
}
setIsAuthenticated(true);
setIsLoading(false);
// 有効期限ちょうどに未認証状態へ切り替える
timer = setTimeout(clearAuth, msUntilExpiry);
} catch {
clearAuth();
setIsLoading(false);
}
return () => clearTimeout(timer);
}, []);
return { isAuthenticated, isLoading };
};
export default useAuth;
ヘッダ
src/frontend/src/app/components/Header.tsx
"use client"; // <- 追加
import Link from "next/link";
import { STYLES, SETTINGS } from "@/constants";
import useAuth from "@/hooks/useAuth"; // <- 追加
import AccountMenu from "./ui/header/AccountMenu";
import { ButtonSignUp } from "./auth/ButtonSignUp";
import { ButtonSignIn } from "./auth/ButtonSignIn";
const Header = () => {
// ↓追加
// JWT の有効期限を監視し、タイムアウト時に自動で未認証状態へ切り替える
const { isAuthenticated } = useAuth();
// ↑追加
return (
<header className={`${STYLES.HEADER}`}>
<div>
<h1 className={`${STYLES.HEADER_APP_NAME}`}>
<Link href="/">{SETTINGS.APP_NAME}</Link>
</h1>
</div>
<div>
<nav className={`${STYLES.HEADER_MENU}`}>
<Link href="/about/">About</Link>
{/* ↓修正 */}
{!isAuthenticated ? (
<>
<ButtonSignUp />
<ButtonSignIn />
</>
) : (
<AccountMenu />
)}
{/* ↑修正 */}
</nav>
</div>
</header>
);
};
export default Header;
バックエンドサーバを起動した状態で、ブラウザで下記 URL にアクセスしてください。
ヘッダのSign Inからサインインすると、Sign Upボタン、Sign Inボタンが消え、右上にアカウントメニューが表示されます。また、アカウントメニューから、Sign Outをクリックすると、ヘッダ右上がSign Upボタン、Sign Inに戻ります。