FastAPI + JWT Authentication: Implementing Simple User Login Verification

1. Environment Preparation and Dependency Installation

To implement user login authentication with FastAPI and JWT, we first need to install the required Python libraries. Open your terminal and run the following command:

pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart
  • FastAPI: A high-performance web framework for building APIs
  • uvicorn: An ASGI server for running FastAPI applications
  • python-jose: Used for generating and verifying JWT tokens
  • passlib: For password hashing (secure password storage)
  • bcrypt: A dependency of passlib for password encryption algorithms
  • python-multipart: Handles form data (commonly used in login interfaces)

2. Core Concept Understanding

  • JWT (JSON Web Token): A stateless authentication method where the server generates an encrypted token based on user information. Clients can prove their identity by including this token in each request.
  • Dependency: A mechanism in FastAPI for reusing logic (e.g., token verification). It automatically processes requests and returns results.
  • Password Hashing: Passwords are not stored in plain text. Instead, they are converted into irreversible hash values using algorithms like bcrypt. Verification is done by comparing hash values.

3. Code Implementation Steps

3.1 Import Required Libraries
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
3.2 Configure JWT Parameters
# Configure JWT parameters (use environment variables in production)
SECRET_KEY = "your-secret-key-keep-it-safe-and-long-enough"  # Recommend 16+ random characters
ALGORITHM = "HS256"  # Encryption algorithm (HS256 = HMAC-SHA256)
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # Token validity: 30 minutes
3.3 Simulate User Database

Use a list to simulate database storage (replace with real databases like SQLite/PostgreSQL in production):

# Simulated user data (replace with actual database queries in production)
fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "hashed_password": "hashed_secret_password",  # Will be generated using bcrypt later
        "disabled": False,
    }
}
3.4 Password Hashing Tool (passlib)

Use the bcrypt algorithm to hash passwords:

# Initialize password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def get_password_hash(password: str) -> str:
    """Hash a password for storage"""
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    """Verify a stored password against its hash"""
    return pwd_context.verify(plain_password, hashed_password)
3.5 JWT Utility Functions (Generate and Verify Tokens)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    """Generate a JWT token"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

def verify_token(token: str, credentials_exception) -> dict:
    """Verify token validity"""
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")  # Extract username from token (sub = subject)
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    return {"username": username}
3.6 OAuth2 Dependency (Extract Token)

FastAPI provides OAuth2PasswordBearer to automatically retrieve tokens from request headers:

# OAuth2 scheme: Automatically extract Bearer Token from request headers
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
3.7 Define User Models

Use Pydantic to define request/response data structures (ensure data format correctness):

class User(BaseModel):
    username: str
    disabled: Optional[bool] = None

class UserInDB(User):
    hashed_password: str
3.8 Login Endpoint (Get Token)

Accept username/password, verify, and return a token:

# Login endpoint: Path is /token, accepts form parameters (username/password)
@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db=fake_users_db):
    # 1. Retrieve user from database (replace with actual query logic)
    user = db.get(form_data.username)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    # 2. Verify password
    if not verify_password(form_data.password, user["hashed_password"]):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    # 3. Generate Token
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user["username"]}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}
3.9 Protected Endpoint (Verify Token)

Use dependencies to verify the token and return user information:

# Dependency: Verify token and return user info
async def get_current_user(
    token: str = Depends(oauth2_scheme),
    credentials_exception=HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid authentication credentials",
        headers={"WWW-Authenticate": "Bearer"},
    ),
):
    user = verify_token(token, credentials_exception)
    return User(username=user["username"])

# Example of a protected route
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
    return {"user": current_user}
3.10 Main Program Entry
app = FastAPI()

# Initialize password hashing (called during user registration in production; pre-hash for demo)
if __name__ == "__main__":
    # Manually generate hashed password for test user (should be called in registration API)
    test_password = "testpassword"
    fake_users_db["johndoe"]["hashed_password"] = get_password_hash(test_password)

4. Running and Testing

  1. Start the service: In the directory containing your code file (e.g., main.py), run:
   uvicorn main:app --reload
  1. Access Swagger UI: Open a browser and visit http://localhost:8000/docs to test the login endpoint via the interactive interface.
  2. Testing Flow:
    - Call the POST /token endpoint with username=johndoe and password=testpassword to obtain an access_token.
    - Use the token to call the GET /users/me endpoint. Add Authorization: Bearer <token> in the request headers to receive user information.

5. Key Knowledge Summary

  • Dependency: Reuse token verification logic to automatically return user information or block invalid requests.
  • JWT Security: Protect the SECRET_KEY (use environment variables in production) to prevent token expiration or leaks.
  • Password Storage: Use passlib hashing algorithms to store passwords (never store plain text) to mitigate data breach risks.

With the above steps, you have implemented a basic user login authentication system based on FastAPI and JWT. Subsequent extensions could include features like refresh tokens and multi-user role permissions.

Xiaoye