Python API for User Registration & Login – Full Beginner’s Guide

Python API for User Registration & Login – Full Beginner’s Guide

Goal: Build a simple API to register and log in users, store them in MySQL, hash passwords securely using Argon2, and return a JWT token upon login. Fully testable via Postman or curl.

Prerequisites

  • Python 3.9+ (tested with Python 3.13)

  • MySQL running locally (root user, no password)

  • Terminal / command line

  • Code editor (VS Code, PyCharm, etc.)

Step 1 — Create Project & Virtual Environment

mkdir python-api cd python-api python3 -m venv venv source venv/bin/activate # macOS / Linux # Windows: venv\Scripts\activate

Step 2 — Install Dependencies

On macOS zsh, quote extras like passlib[argon2] and pydantic[email].

pip install fastapi uvicorn sqlalchemy pymysql "passlib[argon2]" "pydantic[email]" python-jose

Verify installations:

python -c "import fastapi; print('fastapi OK')" python -c "import sqlalchemy; print('sqlalchemy OK')" python -c "import pymysql; print('pymysql OK')" python -c "from passlib.context import CryptContext; print('passlib OK')" python -c "from pydantic import BaseModel; print('pydantic OK')" python -c "from jose import jwt; print('python-jose OK')"

All should print OK without errors.

Step 3 — Project Structure

python-api/ ├─ main.py ├─ database.py ├─ models.py ├─ schemas.py ├─ auth.py ├─ crud.py ├─ init_db.py

Step 4 — Create Project Files

database.py

from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker DATABASE_URL = "mysql+pymysql://root:@localhost:3306/fastapi_auth" engine = create_engine(DATABASE_URL, echo=False) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() def get_db(): db = SessionLocal() try: yield db finally: db.close()

models.py

from sqlalchemy import Column, Integer, String from database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, index=True, nullable=False) email = Column(String(100), unique=True, index=True, nullable=False) password = Column(String(255), nullable=False)

schemas.py

from pydantic import BaseModel, EmailStr class UserRegister(BaseModel): username: str email: EmailStr password: str class UserLogin(BaseModel): username: str password: str class Token(BaseModel): access_token: str token_type: str

auth.py

from passlib.context import CryptContext from datetime import datetime, timedelta from jose import jwt import hashlib SECRET_KEY = "change_this_to_a_random_secret_in_prod" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 pwd_context = CryptContext(schemes=["argon2"], deprecated="auto") def _prehash(password: str) -> str: return hashlib.sha256(password.encode("utf-8")).hexdigest() def hash_password(password: str) -> str: return pwd_context.hash(_prehash(password)) def verify_password(password: str, hashed: str) -> bool: return pwd_context.verify(_prehash(password), hashed) def create_access_token(data: dict, expires_minutes: int = ACCESS_TOKEN_EXPIRE_MINUTES) -> str: to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=expires_minutes) to_encode.update({"exp": expire}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

crud.py

from sqlalchemy.orm import Session from models import User from auth import hash_password, verify_password def get_user_by_username(db: Session, username: str): return db.query(User).filter(User.username == username).first() def get_user_by_email(db: Session, email: str): return db.query(User).filter(User.email == email).first() def create_user(db: Session, username: str, email: str, password: str): user = User(username=username, email=email, password=hash_password(password)) db.add(user) db.commit() db.refresh(user) return user def authenticate_user(db: Session, username: str, password: str): user = get_user_by_username(db, username) if not user or not verify_password(password, user.password): return None return user

init_db.py

from database import Base, engine from models import User print("Creating database tables...") Base.metadata.create_all(bind=engine) print("Done.")

main.py

from fastapi import FastAPI, Depends, HTTPException from sqlalchemy.orm import Session from database import get_db, Base, engine from schemas import UserRegister, UserLogin, Token from crud import get_user_by_username, get_user_by_email, create_user, authenticate_user from auth import create_access_token # Ensure tables exist Base.metadata.create_all(bind=engine) app = FastAPI(title="FastAPI MySQL Auth") @app.post("/register") def register(user: UserRegister, db: Session = Depends(get_db)): if get_user_by_username(db, user.username): raise HTTPException(status_code=400, detail="Username already exists") if get_user_by_email(db, user.email): raise HTTPException(status_code=400, detail="Email already exists") create_user(db, user.username, user.email, user.password) return {"msg": "User registered successfully"} @app.post("/login", response_model=Token) def login(user: UserLogin, db: Session = Depends(get_db)): db_user = authenticate_user(db, user.username, user.password) if not db_user: raise HTTPException(status_code=401, detail="Invalid username or password") token = create_access_token({"sub": db_user.username}) return {"access_token": token, "token_type": "bearer"}

Step 5 — Create MySQL Database

Open MySQL shell:

CREATE DATABASE IF NOT EXISTS fastapi_auth;

Then, in your project folder:

python3 init_db.py

You should see:

Creating database tables... Done.

Step 6 — Start FastAPI Server

uvicorn main:app --reload

Swagger UI: http://127.0.0.1:8000/docs

Step 7 — Test Endpoints

Register

  • POST http://127.0.0.1:8000/register

  • Body (JSON):

{ "username": "souy", "email": "souy@example.com", "password": "123456" }
  • Expected Response:

{"msg":"User registered successfully"}

Login

  • POST http://127.0.0.1:8000/login

  • Body (JSON):

{ "username": "souy", "password": "123456" }
  • Expected Response:

{ "access_token": "eyJ...your_jwt...", "token_type": "bearer" }

Use Authorization: Bearer <access_token> for future protected endpoints.

Step 8 — Test with curl (Optional)

curl -X POST http://127.0.0.1:8000/register \ -H "Content-Type: application/json" \ -d '{"username":"souy","email":"souy@example.com","password":"123456"}' curl -X POST http://127.0.0.1:8000/login \ -H "Content-Type: application/json" \ -d '{"username":"souy","password":"123456"}'

Step 9 — Troubleshooting

  • 500 Internal Server Error: Check terminal for traceback.

  • email-validator missing: Run pip install "pydantic[email]".

  • bcrypt 72-byte error: Avoided by SHA256 + Argon2.

  • MySQL password issue: Update DATABASE_URL if your root has a password.

  • macOS zsh install error: Quote extras like "passlib[argon2]".

Done! You now have a fully functional FastAPI user registration & login system with MySQL and JWT authentication.

Souy Soeng

Souy Soeng

Hi there ๐Ÿ‘‹, I’m Soeng Souy (StarCode Kh)
-------------------------------------------
๐ŸŒฑ I’m currently creating a sample Laravel and React Vue Livewire
๐Ÿ‘ฏ I’m looking to collaborate on open-source PHP & JavaScript projects
๐Ÿ’ฌ Ask me about Laravel, MySQL, or Flutter
⚡ Fun fact: I love turning ☕️ into code!

Post a Comment

CAN FEEDBACK
close