BudiBadu Logo
Samplebadu

FastAPI by Example: Password Hashing Strategy

FastAPI 0.100+

Storing passwords securely is non-negotiable. This code example demonstrates how to use PassLib with the bcrypt algorithm to hash passwords before saving them and verify them during login.

Code

from passlib.context import CryptContext
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# 1. Setup the context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

class User(BaseModel):
    username: str
    password: str

# 2. Helper functions
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

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

# Mock database
fake_users_db = {}

@app.post("/signup")
async def signup(user: User):
    # Hash the password before storing
    hashed_pw = get_password_hash(user.password)
    fake_users_db[user.username] = {
        "username": user.username,
        "hashed_password": hashed_pw
    }
    return {"msg": "User created"}

@app.post("/login")
async def login(user: User):
    db_user = fake_users_db.get(user.username)
    if not db_user:
        return {"error": "User not found"}
        
    # Verify the password
    if not verify_password(user.password, db_user["hashed_password"]):
        return {"error": "Invalid password"}
        
    return {"msg": "Login successful"}

Explanation

Never store passwords in plain text. Instead, you should hash them using a strong, slow algorithm like bcrypt or Argon2. PassLib is the standard Python library for this, and CryptContext allows you to manage multiple hashing schemes, enabling you to migrate to newer algorithms in the future without breaking existing hashes.

The hash() function generates a salted hash of the password. A salt is random data added to the password before hashing, ensuring that two users with the same password have different hashes. This protects against rainbow table attacks, where attackers use precomputed tables of hashes to reverse-engineer passwords.

The verify() function checks if a plain-text password matches a stored hash. It handles the complexity of extracting the salt and algorithm from the hash string and re-computing the hash for comparison. This process is intentionally slow (CPU intensive) to make brute-force attacks difficult and is designed to be safe against timing attacks.

Code Breakdown

8
CryptContext(schemes=["bcrypt"]) initializes the system. deprecated="auto" allows PassLib to automatically handle upgrades if you change schemes later.
16
pwd_context.verify(...) returns True if the password is correct. It is safe against timing attacks.
19
pwd_context.hash(...) produces a string that includes the algorithm, cost factor, salt, and hash. You store this entire string in your database.
32
fake_users_db simulates a database. In a real app, you would use SQL or NoSQL storage.