Skip to content

Implementing JWT Authentication in FastAPI

JWT (JSON Web Token) is a popular authentication mechanism used to secure APIs. This guide walks you through implementing JWT-based authentication in FastAPI, covering user login, token generation, and route protection.


Why Use JWT Authentication?

JWT is widely used because:

  • Stateless: Tokens are self-contained and do not require server-side storage.
  • Secure: Tokens can include user claims, enabling fine-grained access control.
  • Flexible: Works well for both single-page applications (SPAs) and mobile apps.

Prerequisites

Ensure you have:

  • Python 3.7 or higher
  • FastAPI and Uvicorn installed:
    pip install fastapi uvicorn
    
  • JWT Libraries installed:
    pip install python-jose passlib[bcrypt]
    

Step 1: Setting Up User Authentication

1.1 Create a Simple User Model

from pydantic import BaseModel

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

1.2 Define a Fake User Database

For this tutorial, we’ll use a hardcoded dictionary as our user database:

fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "password": "$2b$12$KIXR8WDFE.zFJI8XB6YiVOS1G2V9PMtPp5O3FmRZK8k4JBZG8nqFS",  # bcrypt for "password"
    }
}

Step 2: Hashing Passwords

2.1 Use Passlib to Hash and Verify Passwords

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

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)

Step 3: Generating JWT Tokens

3.1 Create Utility Functions for JWT

from jose import JWTError, jwt
from datetime import datetime, timedelta

SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict, expires_delta: timedelta | None = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

Step 4: Adding Login Endpoint

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = fake_users_db.get(form_data.username)
    if not user or not verify_password(form_data.password, user["password"]):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token = create_access_token(data={"sub": user["username"]})
    return {"access_token": access_token, "token_type": "bearer"}

Step 5: Protecting Routes with JWT

5.1 Add Dependency for Token Validation

from jose import JWTError, jwt

def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid authentication credentials",
                headers={"WWW-Authenticate": "Bearer"},
            )
        return username
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

5.2 Create a Protected Endpoint

@app.get("/users/me")
async def read_users_me(current_user: str = Depends(get_current_user)):
    return {"username": current_user}

Wrapping Up

With JWT authentication implemented, your FastAPI application is now secure and ready to handle user authentication. For more advanced use cases, like refresh tokens or role-based access control, extend the code as needed.

For a production-ready boilerplate with advanced features and best practices, check out Craft Your Startup.