Skip to content

JWT Token Generation and Verification

🎯 What You’ll Learn

  • What JWT tokens are and why they’re used
  • How to generate JWTs after successful login
  • How to verify JWTs on protected routes
  • How to integrate FastAPI’s OAuth2PasswordBearer for token-based access control

🧠 What Is a JWT?

JWT stands for JSON Web Token. It’s a compact, URL-safe string used to represent claims between two parties — typically a server and a client.

A JWT has three parts:

header.payload.signature

Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlbmJyaWNvIiwiaWF0IjoxNjk5ODg3MjAwfQ.abc123...

🔍 What’s inside?

  • Header: Specifies the algorithm (e.g. HS256)
  • Payload: Contains claims like sub (subject = username), exp (expiration)
  • Signature: Ensures the token hasn’t been tampered with

JWTs are stateless — the server doesn’t store them. Instead, it verifies the signature on each request.


🔐 Step 1: Install JWT Library

pip install python-jose

We’ll use python-jose to encode and decode JWTs.


🛠️ Step 2: Create JWT Utility Functions

📄 core/jwt.py

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

SECRET_KEY = "your-secret-key"  # Use a strong, random key in production
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

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

def decode_access_token(token: str) -> dict | None:
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    except JWTError:
        return None

🔍 What’s happening?

  • create_access_token() adds an expiration and signs the token
  • decode_access_token() verifies the signature and returns the payload

🔑 Step 3: Update Login to Return a JWT

📄 routers/auth.py

from core.jwt import create_access_token

@router.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends(), session: Session = Depends(get_session)):
    user = session.exec(select(User).where(User.username == form_data.username)).first()
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(status_code=401, detail="Invalid credentials")

    token = create_access_token({"sub": user.username})
    return {"access_token": token, "token_type": "bearer"}

🔍 Why sub?

  • sub stands for “subject” — it identifies the user
  • You’ll use it later to fetch the current user from the token

🧩 Step 4: Protect Routes with Token Verification

📄 core/dependencies.py

from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from core.jwt import decode_access_token

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")

def get_current_user(token: str = Depends(oauth2_scheme)) -> str:
    payload = decode_access_token(token)
    if not payload or "sub" not in payload:
        raise HTTPException(status_code=401, detail="Invalid or expired token")
    return payload["sub"]

🔍 What’s happening?

  • OAuth2PasswordBearer tells FastAPI to expect a Bearer <token> header
  • get_current_user() decodes the token and extracts the username

🔐 Step 5: Use get_current_user in Protected Routes

📄 routers/tasks.py

from core.dependencies import get_current_user

@router.get("/", dependencies=[Depends(get_current_user)])
def get_all_tasks(...):
    ...

Now this route requires a valid token. If the token is missing, invalid, or expired, FastAPI returns a 401 error.


🧪 Try It in Swagger UI

  1. POST /auth/login → Get a token
  2. Click “Authorize” in Swagger UI and paste the token
  3. Access protected routes like /tasks

FastAPI automatically adds the Authorization: Bearer <token> header.


🧠 Recap

You now have:

Component Purpose
JWT token Proves user identity
create_access_token() Generates signed token
decode_access_token() Verifies and extracts claims
get_current_user() Injects user identity into routes

This enables secure, stateless authentication across your API.


🧪 Practice Challenge

Add:

  • A GET /me endpoint that returns the current user from the token
  • A token_expired check that returns 401 if the token is too old
  • A refresh_token() function that issues a new token with a fresh expiration