article

Tuesday, April 18, 2023

React-JS and Python Flask Login Token Authentication flask_jwt_extended with Profile | SQLAlchemy.

React-JS and Python Flask Login Token Authentication flask_jwt_extended with Profile | SQLAlchemy.

Python Flask 

https://flask.palletsprojects.com/en/2.2.x/installation/
 
Create an environment
C:\flask_dev>py -3 -m venv venv

Activate the environment
C:\flask_dev>venv\Scripts\activate

Install Flask
venv C:\flask_dev>pip install Flask
C:\flask_dev\flaskreact\app.py

Install requirements

Flask-JWT-Extended
Flask-JWT-Extended not only adds support for using JSON Web Tokens (JWT) to Flask for protecting routes, but also many helpful (and optional) features built in to make working with JSON Web Tokens easier.
https://pypi.org/project/Flask-JWT-Extended/

(venv) PS C:\flask_dev\flaskreact>pip install Flask-JWT-Extended

Flask-Bcrypt
Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application.
https://pypi.org/project/Flask-Bcrypt/

(venv) PS C:\flask_dev\flaskreact>pip install Flask-Bcrypt

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\flaskreact>pip install -U Flask-SQLAlchemy
C:\flask_dev\flaskreact\app.py

Flask-Cors
A Flask extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible.
https://pypi.org/project/Flask-Cors/

(venv) PS C:\flask_dev\flaskreact>pip install -U flask-cors

C:\flask_dev\flaskreact\app.py

#C:\flask_dev\flaskreact\app.py
import json
from flask import Flask, request, jsonify
from datetime import datetime, timedelta, timezone
from flask_jwt_extended import create_access_token,get_jwt,get_jwt_identity, unset_jwt_cookies, jwt_required, JWTManager #pip install Flask-JWT-Extended = https://pypi.org/project/Flask-JWT-Extended/
from flask_bcrypt import Bcrypt #pip install Flask-Bcrypt = https://pypi.org/project/Flask-Bcrypt/
from flask_cors import CORS #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors

from models import db, User

api = Flask(__name__)
CORS(api, supports_credentials=True)

api.config['SECRET_KEY'] = 'cairocoders-ednalan'
api.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration  Mysql                            Username:password@hostname/databasename
#app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:''@localhost/flaskreact'

api.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
jwt = JWTManager(api)

SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True

bcrypt = Bcrypt(api)    
db.init_app(api)
 
with api.app_context():
    db.create_all()

@api.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@api.route('/logintoken', methods=["POST"])
def create_token():
    email = request.json.get("email", None)
    password = request.json.get("password", None)
 
    user = User.query.filter_by(email=email).first()
    #if email != "test" or password != "test":
    #    return {"msg": "Wrong email or password"}, 401
    if user is None:
        return jsonify({"error": "Wrong email or passwords"}), 401
     
    if not bcrypt.check_password_hash(user.password, password):
        return jsonify({"error": "Unauthorized"}), 401
     
    access_token = create_access_token(identity=email)
    #response = {"access_token":access_token}
 
    return jsonify({
        "email": email,
        "access_token": access_token
    })
    #return response

@api.route("/signup", methods=["POST"])
def signup():
    email = request.json["email"]
    password = request.json["password"]
  
    user_exists = User.query.filter_by(email=email).first() is not None
  
    if user_exists:
        return jsonify({"error": "Email already exists"}), 409
      
    hashed_password = bcrypt.generate_password_hash(password)
    new_user = User(name="cairocoders Ednalan", email=email, password=hashed_password, about="sample about me")
    db.session.add(new_user)
    db.session.commit()
  
    return jsonify({
        "id": new_user.id,
        "email": new_user.email
    })

@api.after_request
def refresh_expiring_jwts(response):
    try:
        exp_timestamp = get_jwt()["exp"]
        now = datetime.now(timezone.utc)
        target_timestamp = datetime.timestamp(now + timedelta(minutes=30))
        if target_timestamp > exp_timestamp:
            access_token = create_access_token(identity=get_jwt_identity())
            data = response.get_json()
            if type(data) is dict:
                data["access_token"] = access_token 
                response.data = json.dumps(data)
        return response
    except (RuntimeError, KeyError):
        # Case where there is not a valid JWT. Just return the original respone
        return response

@api.route("/logout", methods=["POST"])
def logout():
    response = jsonify({"msg": "logout successful"})
    unset_jwt_cookies(response)
    return response

@api.route('/profile/<getemail>')
@jwt_required() 
def my_profile(getemail):
    print(getemail)
    if not getemail:
        return jsonify({"error": "Unauthorized Access"}), 401
      
    user = User.query.filter_by(email=getemail).first()
 
    response_body = {
        "id": user.id,
        "name": user.name,
        "email": user.email,
        "about" : user.about
    }
 
    return response_body
C:\flask_dev\flaskreact\models.py
#C:\flask_dev\flaskreact\models.py
from flask_sqlalchemy import SQLAlchemy
from uuid import uuid4
 
db = SQLAlchemy()
 
def get_uuid():
    return uuid4().hex
 
class User(db.Model):
    __tablename__ = "users"
    id = db.Column(db.String(11), primary_key=True, unique=True, default=get_uuid)
    name = db.Column(db.String(150), unique=True)
    email = db.Column(db.String(150), unique=True)
    password = db.Column(db.Text, nullable=False)
    about = db.Column(db.Text, nullable=False)
Postman
Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster.
https://www.postman.com/

Login
Post : http://127.0.0.1:5000/logintoken
Body - row - json
{
"email":"cairocoders@gmail.com",
"password":"123456"
}
Sign Up
Post : http://127.0.0.1:5000/signup
Body - row - json
{
"email":"cairocoders@gmail.com",
"password":"123456"
}
Profile
Get : http://127.0.0.1:5000/profile/cairocoders@gmail.com

Headers :
Key : Authorization
value : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY

Log Out
Get : http://127.0.0.1:5000/logout

run (venv) C:\flask_dev\flaskreact>flask run

React JS
https://create-react-app.dev/

Create Project
C:\react-js>npx create-react-app myreactdev

Run
C:\react-js\myreactdev> npm start
C:\react-js\myreactdev\src\App.js
//C:\react-js\myreactdev\src\App.js
import React, { } from 'react';
import './App.css';
  
import {BrowserRouter, Routes, Route} from 'react-router-dom';

import Login from './components/Login'
import Header from './components/Header'
import Profile from './components/Profile'
import useToken from './components/useToken'

function App() {
	
	const { token, removeToken, setToken } = useToken();
	
	return (
		<div className="vh-100 gradient-custom">
		<div className="container">
		  <h1 className="page-header text-center">React-JS and Python Flask Login Token Authentication flask_jwt_extended with Profile | SQLAlchemy.</h1>
			
		  <BrowserRouter>
			<Header token={removeToken}/>
			{!token && token!=="" &&token!== undefined?  
			<Login setToken={setToken} />
			:(
			  <>
				<Routes>
				  <Route exact path="/profile" element={<Profile token={token} setToken={setToken}/>}></Route>
				</Routes>
			  </>
			)}
		  </BrowserRouter>
		</div>
		</div>
	);
}
    
export default App;
Install React Router Dom
https://www.npmjs.com/package/react-router-dom
C:\react-js\myreactdev>npm i react-router-dom --save

Install Axios
https://www.npmjs.com/package/axios
C:\react-js\myreactdev>npm install axios --save

C:\react-js\myreactdev\src\components\Login.js
//C:\react-js\myreactdev\src\components\Login.js
import React, { useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";

function Login(props) {
     
    const [loginForm, setloginForm] = useState({
      email: "",
      password: ""
    })

	const navigate = useNavigate();
	
    function btnlogin(event) {
		axios({
			method: "POST",
			url:"http://127.0.0.1:5000/logintoken",
			data:{
			  email: loginForm.email,
			  password: loginForm.password
			 }
		})
		.then((response) => {
			console.log(response)
			props.setToken(response.data.access_token)
			alert("Successfully Login");
			localStorage.setItem('email', loginForm.email)
			navigate('/profile');
		}).catch((error) => {
			if (error.response) {
			  console.log(error.response)
			  console.log(error.response.status)
			  console.log(error.response.headers)
				if (error.response.status === 401) {
                    alert("Invalid credentials");
                }
			}
		})

		setloginForm(({
			email: "",
			password: ""}))

		event.preventDefault()
    }

    function handleChange(event) { 
      const {value, name} = event.target
      setloginForm(prevNote => ({
          ...prevNote, [name]: value})
      )}

    let imgs = [
      'https://as1.ftcdn.net/v2/jpg/03/39/70/90/1000_F_339709048_ZITR4wrVsOXCKdjHncdtabSNWpIhiaR7.jpg',
    ];
	
    return (
    <div>
        <div className="container h-50">
          <div className="container-fluid h-custom">
            <div className="row d-flex justify-content-center align-items-center h-50">
              <div className="col-md-9 col-lg-6 col-xl-5">
                <img src={imgs[0]} className="img-fluid"/>
              </div>
              <div className="col-md-8 col-lg-6 col-xl-4 offset-xl-1">
                <form>
                  <div className="d-flex flex-row align-items-center justify-content-center justify-content-lg-start">
                    <p className="lead fw-normal mb-0 me-3">Log Into Your Account</p>
                  </div>
 
                  <div className="form-outline mb-4">
                    <input type="email" value={loginForm.email} onChange={handleChange} text={loginForm.email} name="email" id="form3Example3" className="form-control form-control-lg" placeholder="Enter a valid email address" />
                    <label className="form-label" for="form3Example3">Email address</label>
                  </div>
 
             
                  <div className="form-outline mb-3">
                    <input type="password" value={loginForm.password} onChange={handleChange} text={loginForm.password} name="password" id="form3Example4" className="form-control form-control-lg" placeholder="Enter password" />
                    <label className="form-label" for="form3Example4">Password</label>
                  </div>
 
                  <div className="d-flex justify-content-between align-items-center">
                    <div className="form-check mb-0">
                      <input className="form-check-input me-2" type="checkbox" value="" id="form2Example3" />
                      <label className="form-check-label" for="form2Example3">
                        Remember me
                      </label>
                    </div>
                    <a href="#!" className="text-body">Forgot password?</a>
                  </div>
 
                  <div className="text-center text-lg-start mt-4 pt-2">
                    <button type="button" className="btn btn-primary btn-lg" onClick={btnlogin} >Login</button>
                    <p className="small fw-bold mt-2 pt-1 mb-0">Don't have an account? <a href="/register" className="link-danger">Register</a></p>
                  </div>
 
                </form>
              </div>
            </div>
          </div>
        </div>
    </div>
  );
  
}
export default Login;
C:\react-js\myreactdev\src\components\Header.js
//C:\react-js\myreactdev\src\components\Header.js
import React, { } from "react";
import axios from "axios";
import {useNavigate} from "react-router-dom";

function Header(props) {

	const navigate = useNavigate();
	
	function logMeOut() {
		axios({
			method: "POST",
			url:"http://127.0.0.1:5000/logout",
		})
		.then((response) => {
			props.token()
			localStorage.removeItem('email')
			navigate("/");
		}).catch((error) => {
			if (error.response) {
				console.log(error.response)
				console.log(error.response.status)
				console.log(error.response.headers)
			}
		})
	}
	
	const logged = localStorage.getItem('email');
	
    return(
		<nav className="navbar navbar-expand-lg bg-light">
		  <div className="container-fluid">
			<a className="navbar-brand" href="#">Cairocoders</a>
			<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
			  <span className="navbar-toggler-icon"></span>
			</button>
			<div className="collapse navbar-collapse" id="navbarSupportedContent">
			  <ul className="navbar-nav me-auto mb-2 mb-lg-0">
				<li className="nav-item">
				  <a className="nav-link active" aria-current="page" href="#">Home</a>
				</li>
				<li className="nav-item">
				  <a className="nav-link" href="#">About</a>
				</li>
			  </ul>
				{!logged?
					<button className="btn btn-outline-success" type="submit">Login</button>
				:<button className="btn btn-outline-danger" type="submit" onClick={logMeOut}>Logout</button>}
			</div>
		  </div>
		</nav>
    )
}

export default Header;
C:\react-js\myreactdev\src\components\useToken.js
//C:\react-js\myreactdev\src\components\useToken.js
import { useState } from 'react';

function useToken() {

  function getToken() {
    const userToken = localStorage.getItem('token'); //https://javascript.info/localstorage
    return userToken && userToken
  }

  const [token, setToken] = useState(getToken());

  function saveToken(userToken) {
    localStorage.setItem('token', userToken);
    setToken(userToken);
  };

  function removeToken() {
    localStorage.removeItem("token");
    setToken(null);
  }

  return {
    setToken: saveToken,
    token,
    removeToken
  }

}

export default useToken;
C:\react-js\myreactdev\src\components\Profile.js
//C:\react-js\myreactdev\src\components\Profile.js
import React, { useState, useEffect } from "react";
import axios from "axios";

function Profile(props) {

    const [profileData, setProfileData] = useState(null)
	
    useEffect(() => {
        getUsers();
    }, []);
	
	const email = localStorage.getItem('email');
	
    function getUsers() { 
		axios({
		  method: "GET",
		  url:`http://127.0.0.1:5000/profile/${email}`, 
		  headers: {
			Authorization: 'Bearer ' + props.token
		  }
		})
		.then((response) => {
			console.log(response)
		  const res =response.data
		  res.access_token && props.setToken(res.access_token)
		  setProfileData(({
			profile_name: res.name,
			profile_email: res.email,
			about_me: res.about}))
		}).catch((error) => {
		  if (error.response) {
			console.log(error.response)
			console.log(error.response.status)
			console.log(error.response.headers)
			}
		})
    }
	
    let imgs = [
      'https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-chat/ava1-bg.webp',
    ];


  return (
	  <div className="container">
		<div className="row d-flex justify-content-center align-items-center h-50">
		  <div className="col col-lg-12">
				<div className="card mb-3">
				
					{profileData && <div className="row g-0">
					<div className="col-md-4 bg-c-lite-green text-center text-white">
						<img src={imgs[0]} className="img-fluid my-5" width="150"/>
						<h5>{profileData.profile_name}</h5>
						<p>Coder</p>
						<i className="far fa-edit mb-5"></i>
					</div>
			
					<div className="col-md-8">
						<div className="card-body p-4">
							<h6>Your profile details:</h6>
							
							<div className="row pt-1">
							<div className="col-6 mb-3">
								<h6>Email</h6>
								<p className="text-muted">{profileData.profile_email}</p>
							</div>
							<div className="col-6 mb-3">
								<h6>Phone</h6>
								<p className="text-muted">123 456 789</p>
							</div>
							</div>
							<h6>About</h6>
							<div className="d-flex justify-content-start">
							{profileData.about_me}
							</div>
						</div>
					</div>	

				</div>
				}
			</div>
		  </div>
		</div>
	</div>
  );
}

export default Profile;
react-js\myreactdev\src\App.css
//react-js\myreactdev\src\App.css
body {
  margin: 0;
  background-color: #ffffff;
}

.h-50 {
	margin-top:50px;
}	

.bg-c-lite-green {
    background: -webkit-gradient(linear, left top, right top, from(#f29263), to(#ee5a6f));
    background: linear-gradient(to right, #ee5a6f, #f29263);
}
react-js\myreactdev\public\index.html
//react-js\myreactdev\public\index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"/>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
Run C:\react-j\myreactdev>npm start
http://localhost:3000/

Related Post