5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React ログイン機能作ってみた。(JSX)

Last updated at Posted at 2023-03-24

はじめに

結論:セキュリティ向上のため、CognitoやAuth0を使用することをお勧めします。

【注意】
今回は上記を使用しませんのでご了承ください。
ご利用される際は、自己責任でご利用ください。

実行環境

reactの実行環境は各自でご用意ください。
npx create-react-appwebpackなど

今回使用する必須パッケージ

package.json
"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をレンダリング

index.js
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'

const target = document.getElementById('root')

createRoot(target).render(
    <App />
)

ログイン認証結果などの格納場所を作るため、AuthProviderを一番外枠として記述します。

App.jsx
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から値を取得する関数
⑤ ログイン状態を管理するための認証基盤
⑥ ①のAuthContextvalueとして値を格納

AuthContext.jsx
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 : ログインしていれば、ログイン後のページに遷移させる関数(*今回の仕様ではログアウトするまで、ログイン画面は表示されません。)
④ ログインページに遷移した際に③の関数を実行する(自動遷移が不要の場合は省いてください)
⑤ ログインしている場合のみ、表示したいページに対して②の関数を使います。

CustomRouter.jsx
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にする

受け取った値を使いやすいように整形してコールバック。
エラーの場合はエラー文をコールバック。

fetch.js
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サーバーにアクセスしてemailpasswordが一致したUserデータを取得。
今回は一致したUserのidを使用していきます。

一致しなかった場合は、エラーメッセージをDBサーバー側から受け取る想定で記述しています。

サーバーに関しての記述は省かせていただきますので、各自でご用意してください。

必要でない限り、全ての情報を取得する必要はありません。
今回はわかりやすいようにidを使用していますが、JWT(Json Web Token)などのtokenを受け取ることをお勧めします。

例)
[{"id":"1","email":"example.com","password":"@^_^@",..."more"},]

ログインページの作成

useAuthContext()から値を受け取る
LoggedInの値が変わったら実行(ログインに成功していれば、Loadingの値をtrueに変更)
emailpasswordが正しければ、resultに値が返ってくる
④ セッション管理としてlocalStrageに値を格納
⑤ ログイン後に扱いやすいようにAuthInfoに値を格納
⑥ ログインが成功したのでLogggedIntrueに変更
⑦ 一致しなかった場合は、エラーメッセージをアラートする
Loadingの値がtrue(ログインが成功している場合)はログイン後のページに遷移

LoginPage.jsx
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

ログイン後のページを用意

① ログインページに遷移
LoggedInfalseに変更
localStrageから認証用データを削除

HomePage.jsx
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版も後日投稿予定です。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?