Python JWT Authentication with RSA Encryption (Step-by-Step)

Python JWT Authentication with RSA Encryption (Step-by-Step)

This tutorial shows how to build a secure, production-ready REST API authentication system using Python (FastAPI).

We use JWT (JSON Web Tokens) for stateless authentication and RSA encryption to encrypt passwords on the client before sending them to the backend. On the server, passwords are decrypted using a private RSA key and verified using bcrypt, backed by a MySQL database.

📌 Tech Stack

  • Python 3.10+

  • FastAPI

  • MySQL

  • JWT (python-jose)

  • RSA Encryption (Public / Private Key)

  • bcrypt

  • Postman

🧠 Authentication Flow

  1. Client encrypts password using RSA Public Key

  2. Server decrypts password using RSA Private Key

  3. Password verified using bcrypt

  4. JWT token issued

  5. Protected routes secured with JWT dependency

📦 Requirements

  • Python 3.10+

  • MySQL 8+

  • OpenSSL

  • Postman

1️⃣ Create Python Project

mkdir python-rsa-auth cd python-rsa-auth python3 -m venv venv source venv/bin/activate

2️⃣ Install Dependencies

pip install fastapi uvicorn python-jose bcrypt mysql-connector-python python-dotenv cryptography

3️⃣ Create Project Folder Structure

mkdir -p app/api/auth \ app/utils \ app/db \ app/middleware \ storage/rsa

Folder Purpose

FolderDescription
app/apiAPI endpoints
app/middlewareJWT auth middleware
app/utilsJWT & RSA helpers
app/dbDatabase connection
storage/rsaRSA key storage

4️⃣ Create Required Files

touch app/main.py \ app/api/auth/register.py \ app/api/auth/login.py \ app/api/auth/profile.py \ app/api/auth/logout.py \ app/middleware/auth.py \ app/utils/jwt.py \ app/utils/rsa.py \ app/db/mysql.py \ .env \ .gitignore

5️⃣ Configure .gitignore

venv/ .env storage/rsa/private.pem

⚠️ Never commit your private RSA key.

6️⃣ Environment Variables (.env)

DB_HOST=localhost DB_USER=root DB_PASSWORD= DB_NAME=python_rsa_auth JWT_SECRET=super_secret_key JWT_EXPIRES=3600

7️⃣ MySQL Database Setup

CREATE DATABASE python_rsa_auth; USE python_rsa_auth; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100) UNIQUE, password VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

8️⃣ Generate RSA Keys (OpenSSL)

openssl genrsa -out storage/rsa/private.pem 2048 openssl rsa -in storage/rsa/private.pem -pubout -out storage/rsa/public.pem
  • public.pem → frontend encryption

  • private.pem → backend decryption

9️⃣ MySQL Connection

app/db/mysql.py

import mysql.connector import os from dotenv import load_dotenv load_dotenv() db = mysql.connector.connect( host=os.getenv("DB_HOST"), user=os.getenv("DB_USER"), password=os.getenv("DB_PASSWORD"), database=os.getenv("DB_NAME") )

🔟 JWT Helper

app/utils/jwt.py

from jose import jwt import os, time def sign_token(user): payload = { "id": user["id"], "email": user["email"], "exp": time.time() + int(os.getenv("JWT_EXPIRES")) } return jwt.encode(payload, os.getenv("JWT_SECRET"), algorithm="HS256") def verify_token(token): return jwt.decode(token, os.getenv("JWT_SECRET"), algorithms=["HS256"])

1️⃣1️⃣ RSA Helper

app/utils/rsa.py

from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding import base64 with open("storage/rsa/private.pem", "rb") as f: private_key = serialization.load_pem_private_key( f.read(), password=None ) def decrypt_password(encrypted): decrypted = private_key.decrypt( base64.b64decode(encrypted), padding.PKCS1v15() ) return decrypted.decode()

1️⃣2️⃣ Register API

app/api/auth/register.py

from fastapi import APIRouter import bcrypt from app.db.mysql import db router = APIRouter() @router.post("/register") def register(data: dict): cursor = db.cursor(dictionary=True) hashed = bcrypt.hashpw( data["password"].encode(), bcrypt.gensalt() ) cursor.execute( "INSERT INTO users (name, email, password) VALUES (%s, %s, %s)", (data["name"], data["email"], hashed) ) db.commit() return {"message": "User registered successfully"}

1️⃣3️⃣ Login API

app/api/auth/login.py

from fastapi import APIRouter, HTTPException import bcrypt from app.utils.rsa import decrypt_password from app.utils.jwt import sign_token from app.db.mysql import db router = APIRouter() @router.post("/login") def login(data: dict): cursor = db.cursor(dictionary=True) cursor.execute("SELECT * FROM users WHERE email=%s", (data["email"],)) user = cursor.fetchone() if not user: raise HTTPException(status_code=401, detail="Invalid credentials") decrypted = decrypt_password(data["password"]) if not bcrypt.checkpw(decrypted.encode(), user["password"].encode()): raise HTTPException(status_code=401, detail="Invalid credentials") return { "user": { "id": user["id"], "name": user["name"], "email": user["email"] }, "access_token": sign_token(user), "token_type": "Bearer" }

1️⃣4️⃣ JWT Middleware

app/middleware/auth.py

from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer from app.utils.jwt import verify_token security = HTTPBearer() def auth_required(credentials=Depends(security)): try: return verify_token(credentials.credentials) except: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token" )

1️⃣5️⃣ Profile API

app/api/auth/profile.py

from fastapi import APIRouter, Depends from app.middleware.auth import auth_required router = APIRouter() @router.get("/profile") def profile(user=Depends(auth_required)): return user

1️⃣6️⃣ Logout API

app/api/auth/logout.py

from fastapi import APIRouter router = APIRouter() @router.post("/logout") def logout(): return {"message": "Logout handled on client side"}

1️⃣7️⃣ Main App

app/main.py

from fastapi import FastAPI from app.api.auth import register, login, profile, logout app = FastAPI() app.include_router(register.router, prefix="/api/auth") app.include_router(login.router, prefix="/api/auth") app.include_router(profile.router, prefix="/api/auth") app.include_router(logout.router, prefix="/api/auth")

▶️ Run Application

uvicorn app.main:app --reload

Base URL:

http://localhost:8000/api/auth

🚀 Postman Testing

✅ Register

POST /register

{ "name": "StarCode Kh", "email": "starcodekh@example.com", "password": "12345678" }

🔐 Encrypt Password (Client)

Use public.pem to encrypt password before login.

Encrypted output → Base64

✅ Login

POST /login

{ "email": "starcodekh@example.com", "password": "ENCRYPTED_PASSWORD_BASE64" }

🔒 Profile (Protected)

GET /profile

Header

Authorization: Bearer YOUR_JWT_TOKEN

🚪 Logout

POST /logout

Header

Authorization: Bearer YOUR_JWT_TOKEN

✅ Production Best Practices

  • Use HTTPS

  • Never commit private.pem

  • Rotate RSA keys

  • Short-lived JWT tokens

  • Rate-limit login attempts

  • Secure .env files

🎯 Final Result

You now have a clean, professional, production-ready Python authentication REST API using:

🔐 RSA-encrypted passwords
🔑 JWT authentication
🛡 MySQL backend

🔗 Source Code

Want the full source code?
👉 Download the complete Python JWT Authentication with RSA example from my GitHub repository

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