React.ContextとCookieによる簡易的な認証制御

## React.Context とは？
propsで階層を順に下って値を引き渡すのではなく、下位階層すべてにプロパティを引き渡しちゃおうというもの。
> コンテクストは各階層で手動でプロパティを下に渡すことなく、コンポーネントツリー内でデータを渡す方法を提供します。
https://ja.legacy.reactjs.org/docs/context.html

## 使い方
想定ケース
* 特定のページをログイン済みユーザーにだけ表示する。
* ログインしていない場合はログインページにリダイレクトさせる。
* ログイン成功したらCookieに情報を残しログイン済みとして扱う。


### 認証管理用コンポーネント
ログイン機能、ログアウト機能を提供。ログインしたらCookieに情報を残す。
```javascript:AuthContext.jsx
import { setCookie } from '../utils/CookieUtil';
import React, { useContext} from 'react';

// 認証情報を共有するためのコンテキストを作成
const AuthContext = React.createContext();

// 認証コンテキストを簡単に利用するためのカスタムフック
export function useAuth() {
  return useContext(AuthContext);
}

// 認証情報を提供するプロバイダーコンポーネント
export default function AuthProvider({ children }) {

  // ユーザーログイン機能
  async function login(id, password) {
    let status = "200";
    let message = "OK";
    // ログイン処理（仮にtest/testで認証OKとする）
    if (id === 'test' && password === 'test') {
      // ログイン成功時の処理
      // クッキーに認証情報を保存
      setCookie('isAuth', 'true', 30);
    } else {
      status = "401";
      message = "Unauthorized";
    }
    return { status, message };
  }

  // ユーザーログアウト機能
  function logout() {
    deleteCookie('isAuth');
    return { status: "200", message: "OK" };
  }

  // コンテキストで提供する値
  const value = {
    login,       // ログイン機能
    logout       // ログアウト機能
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}
```




### ルーター
先ほど作成したAuthContextを設置し、下位階層がログイン管理機能を使用できるように設定。
また、認証が必要なページ（/uop）へのアクセス、かつ、Cookieに情報がない場合は、ログインページ（/login）にリダイレクトする。
```javascript:App.jsx
import { useEffect } from 'react';
import './App.css';
import { BrowserRouter, Route, Routes, useNavigate, useLocation } from 'react-router-dom';
import AuthContext from './contexts/AuthContext.jsx';
import UserOnlyPage from './components/UserOnlyPage.jsx';
import FreePage from './components/FreePage.jsx';
import Loginform from './components/Loginform.jsx';
import { getCookie } from './utils/CookieUtil';

function App() {
  return (
    <AuthContext>
      <BrowserRouter>
        <AuthHandler />
      </BrowserRouter>
    </AuthContext>
  );
}

function AuthHandler() {
  const navigate = useNavigate();
  const location = useLocation();

  useEffect(() => {
    const isAuth = getCookie('isAuth');

    // 認証が必要なルートをチェック
    if (location.pathname === '/uop' && isAuth !== 'true') {
      navigate('/login'); // 認証が必要な場合はログインページにリダイレクト
    }
  }, [navigate, location]);

  return (
    <Routes>
      <Route path='/uop' element={<UserOnlyPage />} />
      <Route path='/free' element={<FreePage />} />
      <Route path='/login' element={<Loginform />} />
    </Routes>
  );
}

export default App;
```

### ログインフォーム
未認証の場合のリダイレクト先。
ログインに成功したらCookieに情報をセットされ、以降は認証済みとして扱われるようになる。
```javascript:LoginForm.jsx
//ログインフォーム
import {React, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
// 認証関連のカスタムフックをインポート
import { useAuth } from '../contexts/AuthContext';
import {deleteCookie} from '../utils/CookieUtil';

export default function Loginform () {
  const [id1, setId1] = useState('');
  const [id2, setId2] = useState('');
  const [password, setPassword] = useState('');
  // 認証関連のカスタムフックを利用
  const { login } = useAuth();
  const navigate = useNavigate();

  useEffect(() => {
    // ログインページに遷移した際にクッキーを削除
    deleteCookie('isAuth');
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      // ログイン処理
      const res = await login(id1 + id2, password); 
      if (res.status === "200") {
        navigate('/');
      } else {
        alert('ログインに失敗しました');
      }
    } catch (error) {
      console.error('login error', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor='id1'>氏名:</label>
      <input
        type='text'
        id='id1'
        value={id1}
        onChange={(e) => setId1(e.target.value)}
      />
      <input
        type='text'
        id='id2'
        value={id2}
        onChange={(e) => setId2(e.target.value)}
      />
      <label htmlFor='password'>Password:</label>
      <input
        type='password'
        id='password'
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type='submit'>Login</button>
    </form>
  );
}
```


### その他：Cookie管理用util
Cookie作成、削除など機能提供用。
react-cookieを使ったほうがいいけど勉強のために自作。
```javascript:CookieUtil.jsx
// クッキー設定関数
export const setCookie = (name, value, minutes) => {
  const d = new Date();
  d.setTime(d.getTime() + (minutes * 60 * 1000)); // 分単位で有効期限を設定
  const expires = "expires=" + d.toUTCString();
  document.cookie = `${name}=${value}; ${expires}; path=/`;
};

// クッキー取得関数
export const getCookie = (name) => {
  const cname = name + "=";
  const decodedCookie = decodeURIComponent(document.cookie);
  const ca = decodedCookie.split(';');
  for (let i = 0; i < ca.length; i++) {
    let c = ca[i];
    while (c.charAt(0) === ' ') c = c.substring(1);
    if (c.indexOf(cname) === 0) return c.substring(cname.length, c.length);
  }
  return "";
};

// クッキー削除関数
export const deleteCookie = (name) => {
  document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
};
```

#### 参考：フォルダ階層
```
src
┗ components
　┗ FreePage.jsx
  ┗ Loginform.jsx
　┗ UserOnlyPage.jsx';
┗ contexts
　┗ AuthContext.jsx
┗ utils
　┗ CookieUtil.jsx
App.jsx

```