🕌

Python ✖️ ReactでTokenを持たせたログイン機能の作成

2024/05/19に公開

## ポートフォリオのため初めての実装なのでお手柔らかに見てください。

実装の手順
1Flaskのバックエンドのセットアップ
2ログインAPIエンドポイントの作成
3Reactフロントエンドのセットアップ
4Reactでの認証状態の管理
5ログインおよびログアウト機能の実装

1
FlaskとFlask-JWTをインストール(まだの方)

pip install Flask Flask-JWT-Extended

login.py

from flask import Flask, jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)

app.config['JWT_SECRET_KEY'] = 'your_secret_key'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 60 * 60  # 1 hour

jwt = JWTManager(app)

users = {
    'user1': {'username': 'nakamura', 'password': generate_password_hash('naaaaa')},
    'user2': {'username': 'kuniyoshi', 'password': generate_password_hash('kuuuuu')}
}

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')

    if username not in users or not check_password_hash(users[username]['password'], password):
        return jsonify({'error': 'Invalid username or password'}), 401

    access_token = create_access_token(identity={'username': username})
    return jsonify({'access_token': access_token})


if __name__ == '__main__':
    app.run(debug=True)

corsの有効化も忘れずに

2
/loginのエンドポイントはユーザー名とパスワードを検証することで
資格情報が正しい場合はJWTトークンを返す

1フロントで入力
2バックで合ってるかどうか確認あってたらトークンプレゼント
3トークンもらえたら画面が変わる

こんな感じ

必要な依存関係のものはインストールしてね

認証状態管理Hooksはこんな感じ

import { createContext, useState, useEffect } from 'react';

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [isLoggedIn, setIsLoggedIn] = useState(!!localStorage.getItem('token'));
  const [username, setUsername] = useState(localStorage.getItem('username') || '');

  useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) {
      setIsLoggedIn(true);
      setUsername(localStorage.getItem('username'));
    }
  }, []);

  const login = (user, token) => {
    setIsLoggedIn(true);
    setUsername(user);
    localStorage.setItem('token', token);
    localStorage.setItem('username', user);
  };

  const logout = () => {
    setIsLoggedIn(false);
    setUsername('');
    localStorage.removeItem('token');
    localStorage.removeItem('username');
  };

  return (
    <AuthContext.Provider value={{ isLoggedIn, username, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

フロント画面に相当する部分
index.js

import React, { useState, useContext } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import { AuthContext } from './AuthContext';

const Login = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const { login } = useContext(AuthContext);
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await axios.post('http://localhost:5000/login', { username, password });
      login(username, response.data.access_token);
      navigate('/home');
    } catch (error) {
      console.error('Login failed', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
      <button type="submit">Login</button>
    </form>
  );
};

export default Login;

ポイントはこの部分

    <AuthContext.Provider value={{ isLoggedIn, username, login, logout }}>
      {children}
    </AuthContext.Provider>

ログインの認証状態などを管理してるフックスをオブジェクトのコンポーネントにすることで可読性と、他機能でグッと使いやすくなる模様

今回の場合ログイン状態がture出ない場合は、
ログイン画面にしか入れないようにしたい。

ProtectedRoute.js

import React, { useContext } from 'react';
import { Navigate } from 'react-router-dom';
import { AuthContext } from './AuthContext';

const ProtectedRoute = ({ children }) => {
  const { isLoggedIn } = useContext(AuthContext);

  if (!isLoggedIn) {
    return <Navigate to="/" />;
  }

  return children;
};

export default ProtectedRoute;

ポイントとしては
ProtectedRouteが使われた時に
isLoggedInがfalseのときは/に滞在するようにする

これをchildren経由のAuthContext を使用して機能として保持される

これをApp.jsでこのようにして囲むことでこの中のルートなら
AuthContextを使用して認証のやりとりができる。
<ProtectedRoute>
で囲われたコンポーネントはログイン状態がtureでないと表示されなくなる。
このようにしているので

   <AuthProvider>
      <Router>
        <div className="App">
          <h1>Header display area</h1>
          <Routes>
            <Route path="/" element={<Login />} />
            <Route
              path="/Home"
              element={
                <ProtectedRoute>
                  <Home />
                </ProtectedRoute>
              }
            />
            <Route
              path="/editor"
              element={
                <ProtectedRoute>
                  <Python1thPage />
                </ProtectedRoute>
              }
            />
          </Routes>

        </div>
      </Router>
    </AuthProvider>

ちょっとまだまだ定着が甘いのでしっかり勉強したい。

Discussion