👻

Reactの認証管理!useContext を使ったログイン状態の保持と管理

2025/03/20に公開

React で認証を管理する方法 (AuthContext + useAuth)

フロントエンドのアプリケーションでは、ユーザーの認証情報(ログイン状態)を管理する必要があります。

今回は、React の Context API を活用して、認証情報をグローバルに管理する方法を解説します。


1. 認証管理の仕組み

今回の実装では、以下のような仕組みを作ります。

  1. AuthContext を作成 → 認証情報をグローバルに管理
  2. AuthProvider で Context を提供 → アプリ全体で使えるようにする
  3. useAuth カスタムフックを作成 → 簡単に認証情報を取得・更新できるように
  4. ログイン情報を localStorage に保存 → ページをリロードしてもログイン状態を保持
  5. ProtectedRoute を作成 → ログインしていないとアクセスできないページを制御

2. AuthContext の作成

まずは、認証情報を管理する AuthContext を作成します。

context/AuthContext.tsx

import React, { createContext, useContext, useEffect, useState } from "react";

type AuthContextType = {
    isAuthenticated: boolean;
    token: string | null;
    login: (token: string) => void;
    logout: () => void;
    getToken: () => string | null;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
    const [token, setToken] = useState<string | null>(localStorage.getItem("token"));

    useEffect(() => {
        const storedToken = localStorage.getItem("token");
        if (storedToken) {
            setToken(storedToken);
        }
    }, []);

    const login = (newToken: string) => {
        localStorage.setItem("token", newToken);
        setToken(newToken);
    };

    const logout = () => {
        localStorage.removeItem("token");
        setToken(null);
    };

    const getToken = () => token;

    return (
        <AuthContext.Provider value={{ isAuthenticated: !!token, token, login, logout, getToken }}>
            {children}
        </AuthContext.Provider>
    );
};

export const useAuth = () => {
    const context = useContext(AuthContext);
    if (!context) {
        throw new Error("useAuth must be used within an AuthProvider");
    }
    return context;
};

ポイント

  • AuthContext を作成し、認証情報を保持する
  • AuthProvidertoken を管理し、ログイン・ログアウトを処理
  • useAuthcontext を簡単に利用できるようにする

3. 認証を適用する (AuthProvider を使う)

次に、AuthProvider をアプリ全体に適用します。

App.tsx

import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Register from "./pages/Register";
import Dashboard from "./pages/Dashboard";
import ProtectedRoute from "./components/ProtectedRoute";
import { AuthProvider } from "./context/AuthContext";

const App: React.FC = () => {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route path="/register" element={<Register />} />
          <Route element={<ProtectedRoute />}>
            <Route path="/dashboard" element={<Dashboard />} />
          </Route>
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
};

export default App;

ポイント

  • AuthProvider でアプリ全体をラップし、認証情報を提供
  • ProtectedRoute を使って、認証が必要なページを制御

4. ログイン処理を実装

pages/Login.tsx

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import AuthCard from "../components/AuthCard";
import { useAuth } from "../context/AuthContext";

const Login: React.FC = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const navigate = useNavigate();
  const { login } = useAuth();

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();

    try {
      await fetch("http://localhost:9000/sanctum/csrf-cookie", {
        credentials: "include",
      });

      const response = await fetch("http://localhost:9000/api/login", {
        method: "POST",
        credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email, password }),
      });

      const data = await response.json();
      if (response.ok) {
        login(data.token);
        navigate("/dashboard");
      } else {
        setError("ログインに失敗しました。");
      }
    } catch (err) {
      setError("通信エラーが発生しました。");
    }
  };

  return (
    <AuthCard title="ログイン">
      {error && <p className="text-red-500 text-sm mb-4">{error}</p>}
      <form onSubmit={handleLogin} className="space-y-4">
        <input type="email" placeholder="メール" value={email} onChange={(e) => setEmail(e.target.value)} />
        <input type="password" placeholder="パスワード" value={password} onChange={(e) => setPassword(e.target.value)} />
        <button type="submit">ログイン</button>
      </form>
    </AuthCard>
  );
};

export default Login;

ポイント

  • sanctum/csrf-cookie をリクエストして CSRF トークンを取得
  • 成功したら tokenlocalStorage に保存し、ログイン状態を維持

まとめ

AuthContext を作成し、認証情報をグローバルに管理
ログイン状態を localStorage で保持し、リロード時も認証を維持
ProtectedRoute でログイン必須ページを制御

これで React + Laravel API の認証管理 が簡単にできるようになりました! 🎉

Discussion