DEV Community

Jane for Mastering Backend

Posted on • Originally published at blog.masteringbackend.com

Securing Your FastAPI APIs with JWT

Securing Your FastAPI APIs with JWT

In the previous article, we covered the basics of authentication in FastAPI, including the use of sessions, API keys, and basic HTTP authentication. Now, let’s take it a step further and talk about JWT (JSON Web Token) — one of the most popular ways to secure modern APIs.

What is JWT?

JSON Web Tokens (JWTs) are like digital ID cards. They are an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. JWTs are often used for authentication and information exchange in web development.

When you log in, the server assigns you an ID (the token), which you must display on every request to prove your identity.

Unlike traditional sessions, JWTs don’t need the server to “remember” you — everything it needs is inside the token itself.

How JWT Works

JWT authentication follows these steps:

  • User logs in with credentials (username/password)

  • Server validates credentials and generates a JWT token

  • The token is returned to the client

  • Client stores the token (usually in local storage or cookies)

  • For subsequent requests, the client sends the token in the Authorisation header

  • The server validates the token and processes the request if it is valid

JWT Structure

A JSON web token has three parts, separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqYW5lZG9lIiwiZXhwIjoxNzYwMDQ2MTgwfQ.xWdrV_-y3Fnsii_IdIzDXaUjb05FL2-CPal5iwJ_UlU
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

  • Header(green) – tells which algorithm was used to sign the token.

  • Payload(red) – contains your data (like username or role).

  • Signature(blue) – ensures the token hasn’t been changed.

Example:

{
  "sub": "janedoe",
  "role": "user",
  "exp": 1716242622
}
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

Why JWTs are so popular

Stateless

Unlike traditional session-based authentication, JWTs are completely stateless.

There’s no need to store session information in memory or a database — the token itself contains everything the server needs to know about the user.

This means:

  • No need to “remember” users on the backend

  • Easier horizontal scaling (add more servers without worrying about shared sessions)

  • Reduced load on your database or cache

Scalable

Because they don’t rely on centralised session storage, JWTs are ideal for distributed systems or microservice architectures. Each service can independently verify the same token using a shared secret or public key — no coordination required.

That’s why large-scale platforms like Google, GitHub, and Auth0 rely on JWT-based authentication for millions of users daily.

Fast

Authenticating with JWTs is blazing fast.

The server only needs to verify the token’s signature *and *decode its payload ****— no database lookup or session validation required.

This stateless design significantly improves performance, especially under high traffic.

Cross-platform

JWTs are based on open standards (RFC 7519), making them universally compatible across languages, frameworks, and platforms.

Whether your frontend is built in React, Flutter, or Vue, and your backend runs on FastAPI, Node.js, or Go — JWTs work seamlessly for all.

But….

While JWTs offer major advantages, they come with trade-offs you must understand to use them securely and efficiently:

Hard to Invalidate Immediately

Once issued, JWTs remain valid until they expire — unless you implement a token blacklist or revocation mechanism.

If a user logs out or a token is compromised, there’s no easy way to instantly revoke it without additional logic (such as maintaining a deny list or rotating signing keys).

Can Get Large

JWTs contain encoded data (header, payload, signature) in Base64 format.

The more claims you add — user info, roles, permissions — the larger the token becomes.

This can slightly increase request size, especially for mobile or bandwidth-limited clients.

Must Be Stored Securely

JWTs often contain sensitive information and access rights.

If stored improperly (like in localStorage), they can be stolen via XSS attacks.

Safer alternatives include:

  • HTTP-only cookies (cannot be accessed by JavaScript)

  • Secure client storage mechanisms

    Always combine this with HTTPS and proper token expiration.

Implementing JWT Authentication in FastAPI

Setting up JWT in FastAPI

pip install fastapi uvicorn[standard] python-jose[cryptography] passlib[bcrypt] python-multipart
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

Basic Project Structure

app/
├── main.py
├── auth/
│   ├── jwt_handler.py
│   └── routes.py
└── models/
    └── user.py
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

Step 1: Create and Verify Tokens

# app/auth/jwt_handler.py
from datetime import datetime, timedelta
from jose import jwt, JWTError

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

def create_access_token(data: dict, expires_minutes: int = 30):
    data_copy = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=expires_minutes)
    data_copy.update({"exp": expire})
    return jwt.encode(data_copy, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        return None
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

Step 2: Create Auth Routes

# app/auth/routes.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from passlib.context import CryptContext
from .jwt_handler import create_access_token, verify_token

router = APIRouter(prefix="/auth", tags=["Auth"])
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# Mock database
fake_users = {
    "janedoe": {
        "username": "janedoe",
        "hashed_password": pwd_context.hash("password123")
    }
}

@router.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = fake_users.get(form_data.username)
    if not user or not pwd_context.verify(form_data.password, user["hashed_password"]):
        raise HTTPException(status_code=401, detail="Invalid username or password")

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

@router.get("/verify")
def verify(token: str):
    data = verify_token(token)
    If not data:
        raise HTTPException(status_code=401, detail="Invalid or expired token")
    return {"user": data["sub"]}
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

Step 3: Protect Your Routes

# app/main.py
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from app.auth.jwt_handler import verify_token
from app.auth.routes import router as auth_router

app = FastAPI(title="JWT Auth Demo")
app.include_router(auth_router)

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

def get_current_user(token: str = Depends(oauth2_scheme)):
    data = verify_token(token)
    if not data:
        raise HTTPException(status_code=401, detail="Invalid or expired token")
    return data

@app.get("/")
def home():
    return {"message": "Welcome to the public route"}

@app.get("/dashboard")
def dashboard(current_user: dict = Depends(get_current_user)):
    return {"message": f"Welcome {current_user['sub']}! This is a protected route."}
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

Step 4: Test your JWT System

uvicorn app.main:app --reload
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

Then test your API with Swagger:

  • Go to localhost:8000

  • Post auth/login → get the token

  • Copy the token and send it in the headers:

Authorization: Bearer <your_token_here>
Enter fullscreen mode Exit fullscreen mode

Use our Online Code Editor

  1. Go to /dashboard → should return your username.

Best Practices for API Security

  • Use HTTPS: Always use HTTPS to encrypt data in transit

Have a great one!!!

Author: Jane Nkwor


Thank you for being a part of the community

Before you go:

Whenever you’re ready

There are 4 ways we can help you become a great backend engineer:

  • The MB Platform: Join thousands of backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.
  • The MB Academy: The “MB Academy” is a 6-month intensive Advanced Backend Engineering Boot Camp to produce great backend engineers.
  • Join Backend Weekly: If you like posts like this, you will absolutely enjoy our exclusive weekly newsletter, sharing exclusive backend engineering resources to help you become a great Backend Engineer.
  • Get Backend Jobs: Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board.

Top comments (0)