はじめに
結論:セキュリティ向上のため、CognitoやAuth0を使用することをお勧めします。
【注意】
今回は上記を使用しませんのでご了承ください。
ご利用される際は、自己責任でご利用ください。
実行環境
reactの実行環境は各自でご用意ください。
npx create-react-app
やwebpack
など
今回使用する必須パッケージ
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.9.0",
}
ディレクトリ構造
構築した環境によって構造に違いがありますのでご了承ください。
[root]/
┣ src/
┃ ┣ modules/
┃ ┃ ┗ fetch.js
┃ ┣ router/
┃ ┃ ┣ AuthContext.jsx
┃ ┃ ┗ CustomRouter.jsx
┃ ┣ views/
┃ ┃ ┣ pages/
┃ ┃ ┃ ┗ HomePage.jsx
┃ ┃ ┗ LoginPage.jsx
┃ ┣ App.jsx
┃ ┣ index.html
┃ ┗ index.js
┗ package.json
index.jsでApp.jsxをレンダリング
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
const target = document.getElementById('root')
createRoot(target).render(
<App />
)
ログイン認証結果などの格納場所を作るため、AuthProvider
を一番外枠として記述します。
import React from "react";
import AuthProvider from "./router/AuthContext";
import CustomRouter from "./router/CustomRouter";
const App = () => {
return(
<AuthProvider>
<CustomRouter />
</AuthProvider>
)
}
export default App
認証結果の格納場所を作成
① createContext()
を使い値の格納場所を作成します。(値を下の階層に受け渡すための容器)
② ①を扱いやすいようにする関数
③ ブラウザのlocalStrage
に保存する関数
④ ブラウザのlocalStrage
から値を取得する関数
⑤ ログイン状態を管理するための認証基盤
⑥ ①のAuthContext
にvalue
として値を格納
import React, { createContext, useState, useContext } from 'react'
// ①
const AuthContext = createContext();
// ②
export const useAuthContext = () => {
return useContext(AuthContext)
}
// ③
export const setSession = (AuthInfo) => {
localStorage.setItem('AuthInfo', JSON.stringify(AuthInfo))
}
// ④
export const getSession = () => {
const userInfo = localStorage.getItem('AuthInfo')
if (userInfo) {
return JSON.parse(userInfo)
} else {
return {}
}
}
// ⑤
const AuthProvider = ({ children }) => {
const [LoggedIn, setLoggedIn] = useState(false)
const [AuthInfo, setAuthInfo] = useState({})
const [Loading, setLoading] = useState(false)
return (
// ⑥
<AuthContext.Provider value={{
LoggedIn,
setLoggedIn,
AuthInfo,
setAuthInfo,
Loading,
setLoading
}}>
{children}
</AuthContext.Provider>
)
}
export default AuthProvider
認証結果を元に遷移させる機能の作成
① ログインしているかどうかのデータを受け取る
② PrivateRoute
: ログインしていなければ、ログインページに遷移させる関数(URLを直接入力され際の対応)
③ Login_check
: ログインしていれば、ログイン後のページに遷移させる関数(*今回の仕様ではログアウトするまで、ログイン画面は表示されません。)
④ ログインページに遷移した際に③の関数を実行する(自動遷移が不要の場合は省いてください)
⑤ ログインしている場合のみ、表示したいページに対して②の関数を使います。
import React from 'react'
import { BrowserRouter as Router, Navigate, NavLink, Route, Routes } from "react-router-dom";
import LoginPage from '../views/LoginPage'
import HomePage from '../views/pages/HomePage'
import { getSession, useAuthContext } from './AuthContext';
const CustomRouter = () => {
// ①
const {
LoggedIn,
} = useAuthContext()
// ②
const PrivateRoute = ({ children }) => {
if (!LoggedIn && !getSession().id) {
return <Navigate to={"/login"} />
} else {
return children
}
}
// ③
const Login_check = ({ children }) => {
if (getSession().id) {
return <Navigate to={"/"} />
} else {
return children
}
}
return (
<>
<Router>
<Routes>
{/* ④ */}
<Route path="/login" element={<Login_check><LoginPage /></Login_check>} />
<Route path="*" element={<>PAGE NOT FOUND 404</>} />
{/* ⑤ */}
<Route path="/" element={<PrivateRoute><HomePage /></PrivateRoute>} />
</Routes>
</Router>
</>
)
}
export default CustomRouter
使用頻度が高そうなものはmoduleにする
受け取った値を使いやすいように整形してコールバック。
エラーの場合はエラー文をコールバック。
export const fetch_Fnc = async (url, data) => {
return await fetch(url, {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
})
.then(Response => Response.json())
.then(data => data)
.catch(err => err)
}
DB接続用のサーバー
ここでは、DBサーバーにアクセスしてemail
とpassword
が一致したUserデータを取得。
今回は一致したUserのid
を使用していきます。
一致しなかった場合は、エラーメッセージをDBサーバー側から受け取る想定で記述しています。
サーバーに関しての記述は省かせていただきますので、各自でご用意してください。
必要でない限り、全ての情報を取得する必要はありません。
今回はわかりやすいようにid
を使用していますが、JWT(Json Web Token)などのtoken
を受け取ることをお勧めします。
[{"id":"1","email":"example.com","password":"@^_^@",..."more"},]
ログインページの作成
① useAuthContext()
から値を受け取る
② LoggedIn
の値が変わったら実行(ログインに成功していれば、Loading
の値をtrue
に変更)
③ email
とpassword
が正しければ、result
に値が返ってくる
④ セッション管理としてlocalStrage
に値を格納
⑤ ログイン後に扱いやすいようにAuthInfo
に値を格納
⑥ ログインが成功したのでLogggedIn
をtrue
に変更
⑦ 一致しなかった場合は、エラーメッセージをアラートする
⑧ Loading
の値がtrue
(ログインが成功している場合)はログイン後のページに遷移
import React, { useEffect, useState } from 'react'
import { getSession, setSession, useAuthContext } from '../router/AuthContext'
import { fetch_Fnc } from '../modules/fetch'
import { Navigate } from 'react-router-dom'
const LoginPage = () => {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
// ①
const {
LoggedIn,
setLoggedIn,
AuthInfo,
setAuthInfo,
Loading,
setLoading,
} = useAuthContext()
// ②
useEffect(() => {
if (LoggedIn||getSession().id) {
setLoading(true)
}
}, [LoggedIn])
const Login = async () => {
//バリデーションチェック(超簡易版)
if (!email) {
return console.log('メールアドレスを入力してください')
} else if (!password) {
return console.log('パスワードを入力してください')
}
const data = { email: email, password: password }
// URLは各自のサーバー環境に合わせてください
const url = 'http://localhost:8080/login'
const result = await fetch_Fnc(url, data)
if (result[0].id) {
// ④
setSession(result[0])
// ⑤
setAuthInfo(result[0])
// ⑥
setLoggedIn(true)
} else {
// ⑦
return alert(result)
}
}
return (
<>
<div>LoginPage</div>
<input type="email"
value={email}
placeholder='メールアドレス'
onChange={(e) => setEmail(e.currentTarget.value)}
/>
<input type="password"
value={password}
placeholder='パスワード'
onChange={(e) => setPassword(e.currentTarget.value)}
/>
<button onClick={Login}>ログイン</button>
// ⑧
{Loading ? <Navigate to={"/"} /> : ''}
</>
)
}
export default LoginPage
ログイン後のページを用意
① ログインページに遷移
② LoggedIn
をfalse
に変更
③ localStrage
から認証用データを削除
import React from "react";
import { NavLink } from "react-router-dom";
const HomePage = () => {
return (
<>
<div>HomePage</div>
<NavLink
// ①
to={"/login"}
onClick={() =>{
// ②
setLoggedIn(false)
// ③
localStorage.removeItem('AuthInfo')
}}>
ログアウト
</NavLink>
</>
)
}
export default HomePage
完成
追記
TSX版も後日投稿予定です。