article

Monday, February 20, 2023

FastAPI Login Register User Token Authentication with Password Hashing and Send Email SMTP

FastAPI Login Register User Token Authentication with Password Hashing and Send Email SMTP

Install Fastapi

https://github.com/tiangolo/fastapi

pip install fastapi
C:\fastAPI\loginregister>pip install fastapi
C:\fastAPI\loginregister>pip install "uvicorn[standard]"

Install sqlalchemy jinja2
https://pypi.org/project/Jinja2/
pip install python-multipart sqlalchemy jinja2
C:\fastAPI\loginregister>pip install python-multipart sqlalchemy jinja2


Create main.py
loginregister/main.py
#loginregister/main.py
from fastapi import FastAPI, Request, APIRouter,Depends,Form,HTTPException,Response
from starlette.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from connection import Base,engine, sess_db
from sqlalchemy.orm import Session
from scurity import get_password_hash,verify_password, create_access_token, verify_token, COOKIE_NAME
from starlette.responses  import RedirectResponse

# Repository
from repositoryuser import UserRepository, SendEmailVerify

# Model
from models import UserModel

templates = Jinja2Templates(directory="templates")

app = FastAPI()
app.mount("/static",StaticFiles(directory="static",html=True),name="static")

#db engin
Base.metadata.create_all(bind=engine)

@app.get("/")
def home(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

@app.get("/about")
def home(request: Request):
    return templates.TemplateResponse("about.html", {"request": request})

@app.get("/user/signup")
def signup(req: Request):
    return templates.TemplateResponse("signup.html", {"request": req})

@app.post("/signupuser")
def signup_user(db:Session=Depends(sess_db),username : str = Form(),email:str=Form(),password:str=Form()):
    print(username)
    print(email)
    print(password)
    userRepository=UserRepository(db)
    db_user= userRepository.get_user_by_username(username)
    if db_user:
        return "username is not valid"

    signup=UserModel(email=email,username=username,password=get_password_hash(password))
    success=userRepository.create_user(signup)
    token=create_access_token(signup)
    SendEmailVerify.sendVerify(token)
    if success:
        return "create  user successfully"
    else:
        raise HTTPException(
            status_code=401, detail="Credentials not correct"
        )

@app.get("/user/signin")
def login(req: Request):
    return templates.TemplateResponse("/signin.html", {"request": req})

@app.post("/signinuser")
def signin_user(response:Response,db:Session=Depends(sess_db),username : str = Form(),password:str=Form()):
    userRepository = UserRepository(db)
    db_user = userRepository.get_user_by_username(username)
    if not db_user:
        return "username or password is not valid"

    if verify_password(password,db_user.password):
        token=create_access_token(db_user)
        response.set_cookie(
            key=COOKIE_NAME,
            value=token,
            httponly=True,
            expires=1800
        )
        return {COOKIE_NAME:token,"token_type":"cairocoders"}

@app.get('/user/verify/{token}')
def verify_user(token,db:Session=Depends(sess_db)):
    userRepository=UserRepository(db)
    payload=verify_token(token)
    username=payload.get("username")
    db_user=userRepository.get_user_by_username(username)

    if not username:
        raise  HTTPException(
            status_code=401, detail="Credentials not correct"
        )
    if db_user.is_active==True:
        return "your account  has been allreay activeed"

    db_user.is_active=True
    db.commit()
    response=RedirectResponse(url="/user/signin")
    return response
    #http://127.0.0.1:8000/user/verify/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNseWRleTAxMzEiLCJlbWFpbCI6ImNseWRleUBnbWFpbC5jb20iLCJyb2xlIjoidXNlciIsImFjdGl2ZSI6ZmFsc2V9.BKektCLzr47qn-fRtnGVulSdYlcMdemJQO_p32jWDk0
Create database Connection sqlalchemy
loginregister/connection.py
#loginregister/connection.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from typing import Optional

#dbcon = "postgresql://user:password@postgresserver/db"
dbcon = 'sqlite:///fastapidb.sqlite3'

engine = create_engine(dbcon)
SessionFactory = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
Base.metadata.create_all(bind=engine)


DATABASE_URL: Optional[str] = None
SECRET_KEY: Optional[str] = "cairocoders"

def sess_db():
    db = SessionFactory()
    try:
        yield db
    finally:
        db.close()

Create database Model sqlalchemy

loginregister/models.py
#loginregister/models.py
from sqlalchemy import Column,String,Integer,Boolean,Enum
from schema import Roles
from connection import  Base

class UserModel(Base):
    __tablename__ = "users"
    id=Column(Integer,primary_key=True,index=True)
    email=Column(String,unique=True,index=True)
    username=Column(String,unique=True,index=True)
    password=Column(String,unique=False,index=True)
    is_active=Column(Boolean,default=False)
    role=Column(Enum(Roles),default="user")
loginregister/repositoryuser.py

#loginregister/repositoryuser.py
from sqlalchemy.orm import Session
from models import UserModel
from typing import  Dict,Any
from sqlalchemy.orm import Session

import smtplib
from email.message import EmailMessage

class UserRepository:
    def __init__(self,sess:Session):
        self.sess: Session=sess

    def create_user(self,signup:UserModel) -> bool:
         try:
             self.sess.add(signup)
             self.sess.commit()
         except:
             return False
         return True

    def get_user(self):
        return  self.sess.query(UserModel).all()

    def get_user_by_username(self,username:str):
        return self.sess.query(UserModel).filter(UserModel.username==username).first()

    def update_user(self,id:int,details:Dict[str,Any]) -> bool:
        try:
            self.sess.query(UserModel).filter(UserModel.id==id).update(details)
            self.sess.commit()
        except:
            return False
        return True
    def delete_user(self,id:int)-> bool:
        try:
            self.sess.query(UserModel).filter(UserModel.id==id).delete()
            self.sess.commit()
        except:
            return  False
        return  True

class SendEmailVerify:

  def sendVerify(token):
    email_address = "cairocoders0711@gmail.com" # type Email
    email_password = "cairocoders-ednalan" # If you do not have a gmail apps password, create a new app with using generate password. Check your apps and passwords https://myaccount.google.com/apppasswords

    # create email
    msg = EmailMessage()
    msg['Subject'] = "Email subject"
    msg['From'] = email_address
    msg['To'] = "clydeymojica0130@gmail.com" # type Email
    msg.set_content(
       f"""\
    verify account        
    http://localhost:8080/user/verify/{token}
    """,
        
    )
    # send email
    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
        smtp.login(email_address, email_password)
        smtp.send_message(msg)
loginregister/schema.py

#loginregister/schema.py
from datetime import date
from pydantic import BaseModel, EmailStr #pip install pydantic[email]
from enum import Enum
from fastapi import Form


class UserSchema(BaseModel):
    email: EmailStr
    username: str
    password: str

    class Config:
        orm_mode = True

class Roles(Enum):
    user = "user"
    admin = "admin"
loginregister/scurity.py

#loginregister/scurity.py
from passlib.context import  CryptContext #pip install passlib
from fastapi.security import OAuth2PasswordBearer
from jose import jwt #https://pypi.org/project/python-jose/ = pip install python-jose
from fastapi import Depends,Request
from fastapi import HTTPException

from models import UserModel

JWT_SECRET="cairocoders$§%§$Ednalan"
ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=3000

pwd_context=CryptContext(schemes=["bcrypt"],deprecated="auto") #pip install bcrypt
# save token to oauth2_scheme
oauth2_scheme=OAuth2PasswordBearer(tokenUrl="user/signin")
COOKIE_NAME="Authorization"

# create Token
def create_access_token(user):
    try:
        payload={
            "username":user.username,
            "email":user.email,
            "role":user.role.value,
            "active":user.is_active,

        }
        return  jwt.encode(payload,key=JWT_SECRET,algorithm=ALGORITHM)
    except Exception as ex:
        print(str(ex))
        raise ex

# create verify Token
def verify_token(token):
    try:
        payload=jwt.decode(token,key=JWT_SECRET)
        return payload
    except Exception as ex:
        print(str(ex))
        raise ex

# password hash
def  get_password_hash(password):
    return pwd_context.hash(password)

# password verify
def verify_password(plain_password,hashed_password):
    return pwd_context.verify(plain_password,hashed_password)

def get_current_user_from_token(token:str=Depends(oauth2_scheme)):
    user= verify_token(token)
    return user

def get_current_user_from_cookie(request:Request) -> UserModel:
    token=request.cookies.get(COOKIE_NAME)
    if token:
        user = verify_token(token)
        return user
Bootstrap 5
https://getbootstrap.com/docs/5.0/getting-started/introduction/
https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css

loginregister/templates/index.html
//loginregister/templates/index.html
{% extends "base.html" %}

{% block title %}Home Page{% endblock %}

{% block content %}

<div class="event">
  <p><h1>Welcome to the home page.</h1></p>
</div>

{% endblock %}
loginregister/templates/base.html
//loginregister/templates/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', path='/style.css')}}"/>
<title>{% block title %} My Webpage {% endblock %}</title>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="#">Navbar</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav">
            <li class="nav-item active">
              <a class="nav-link" href="/">Home</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="/about">About</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="/user/signin">Login</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="/user/signup">Sign Up</a>
            </li>
          </ul>
        </div>
      </nav>
<div class="container">    
    <div class="row"><p><h3>FastAPI Login Register User Token Authentication with Password Hashing and Send Email SMTP</h3></p>
    {% block content %}{% endblock %}
  </div>  
</div>
</body>
</html>
loginregister/templates/about.html
//loginregister/templates/about.html
{% extends "base.html" %}

{% block title %}About Page{% endblock %}

{% block content %}

<div class="event">
  <p><h1> About page.</h1></p>
</div>

{% endblock %}
loginregister/templates/signin.html
//loginregister/templates/signin.html
{% extends "base.html" %}

{% block title %}Login Page{% endblock %}

{% block content %}
<section style="margin-top: 50px;">
  <div class="container h-custom">
    <div class="row d-flex justify-content-center align-items-center h-100">
      <div class="col-lg-6">
        <img src="{{ url_for('static', path='/login.png')}}" class="img-fluid" alt="Sample image">
      </div>
      <div class="col-lg-6 col-md-6">
        <form action="/signinuser" method="post">
          <div class="d-flex flex-row align-items-center justify-content-center justify-content-lg-start">
            <p class="lead fw-normal mb-0 me-3">Sign in with</p>
            <button type="button" class="btn btn-primary btn-floating mx-1">
              <i class="fab fa-facebook-f"></i>
            </button>
            <button type="button" class="btn btn-primary btn-floating mx-1">
              <i class="fab fa-twitter"></i>
            </button>
            <button type="button" class="btn btn-primary btn-floating mx-1">
              <i class="fab fa-linkedin-in"></i>
            </button>
          </div>

          <div class="divider d-flex align-items-center my-4">
            <p class="text-center fw-bold mx-3 mb-0">Or</p>
          </div>

          <div class="form-outline mb-4">
            <input type="text" id="username" class="form-control form-control-lg" name="username" placeholder="User Name" type="text" value="{{ username }}" />
            <label class="form-label" for="username">User Name</label>
          </div>
          <div class="form-outline mb-3">
            <input type="password" id="pass" name="password" value="{{ password }}" class="form-control form-control-lg" placeholder="Enter password" />
            <label class="form-label" for="pass">Password</label>
          </div>

          <div class="d-flex justify-content-between align-items-center">
            <div class="form-check mb-0">
              <input class="form-check-input me-2" type="checkbox" value="" id="remme" />
              <label class="form-check-label" for="remme">Remember me</label>
            </div>
            <a href="#!" class="text-body">Forgot password?</a>
          </div>

          <div class="text-center text-lg-start mt-4 pt-2">
            <button type="submit" class="btn btn-primary btn-lg" style="padding-left: 2.5rem; padding-right: 2.5rem;">Login</button>
            <p class="small fw-bold mt-2 pt-1 mb-0">Don't have an account? <a href="/user/signup" class="link-danger">Register</a></p>
          </div>
        </form>
      </div>
    </div>
  </div>
</section>
{% endblock %}
loginregister/templates/signup.html
//loginregister/templates/signup.html
{% extends "base.html" %}

{% block title %}Sign Up Page{% endblock %}

{% block content %}
<section style="margin-top: 50px;">
  <div class="container h-custom">
    <div class="row d-flex justify-content-center align-items-center h-100">
      <div class="col-lg-6">
        <img src="{{ url_for('static', path='/reg.svg')}}" class="img-fluid" alt="Sample image">
      </div>
      <div class="col-lg-6 col-md-6">
        <form action="/signupuser" method="post">
          <div class="d-flex flex-row align-items-center justify-content-center justify-content-lg-start">
            <p class="lead fw-normal mb-2 me-3">CREATE AN ACCOUNT</p>
          </div>

          <div class="form-outline mb-4">
            <input type="text" id="username" class="form-control form-control-lg" name="username" placeholder="User Name" value="{{ username }}" />
            <label class="form-label" for="username">User Name</label>
          </div>

          <div class="form-outline mb-4">
            <input type="email" id="mail" class="form-control form-control-lg" name="email" value="{{ email }}" placeholder="Enter a valid email address" />
            <label class="form-label" for="mail">Email address</label>
          </div>

          <div class="form-outline mb-3">
            <input type="password" id="pass" name="password" value="{{ password }}" class="form-control form-control-lg" placeholder="Enter password" />
            <label class="form-label" for="pass">Password</label>
          </div>

          <div class="text-center text-lg-start mt-4 pt-2">
            <button type="submit" class="btn btn-success btn-lg" style="padding-left: 2.5rem; padding-right: 2.5rem;">Register</button>
            <p class="small fw-bold mt-2 pt-1 mb-0">I am already a member? <a href="/user/signin" class="link-danger">Login</a></p>
          </div>
        </form>
      </div>
    </div>
  </div>
</section>
{% endblock %}
loginregister/templates/verify.html
//loginregister/templates/verify.html
{% extends "base.html" %}
{% block title %}Contact Page{% endblock %}

{% block content %}

<div class="wrapper">
  <div class="contacts">
    <h3> verify your account</h3>
  </div>
  <div class="form">
    <h3>Sign in to your email to verify your account</h3>
    <form method="post">
      <div>
        <a class="signin__link" href="/user/signin">back to Login page</a>
      </div>

    </form>
  </div>
</div>

{% endblock %}
run the FastAPI app

C:\fastAPI\loginregister>uvicorn main:app --reload
with uvicorn using the file_name:app_instance open the link on the browser http://127.0.0.1:8000/
http://127.0.0.1:8000/docs

Related Post