JWT Authentication in FastAPI — The Complete Guide for Secure APIs

Learn how to implement JSON Web Token (JWT) authentication in FastAPI the right way.

JWT Authentication in FastAPI — The Complete Guide for Secure APIs
Photo by Fer Troulik on Unsplash

🔐 Say goodbye to confusing auth systems — FastAPI + JWT makes it clean and powerful.

JWT Authentication in FastAPI — The Complete Guide for Secure APIs

Authentication is the gatekeeper of any web application — get it wrong, and your entire API is exposed. But with FastAPI and JWT (JSON Web Tokens), building a secure and modern authentication system becomes not only possible but elegant.

In this guide, we’ll walk through the entire JWT implementation flow in FastAPI, so you can confidently build protected APIs that scale.


Why JWT + FastAPI?

FastAPI is known for its speed and simplicity, and JWT brings a stateless, compact, and widely adopted token-based auth mechanism. Together, they’re a powerhouse for modern backend development.

Here’s why this combo rocks:

  • Stateless authentication — no session storage required
  • Secure and scalable across services
  • Great for APIs, mobile apps, and microservices
  • Fully compatible with OAuth2 flows

What You’ll Learn

In this guide, we’ll cover:

  1. What JWT is and how it works
  2. Setting up FastAPI with authentication dependencies
  3. Creating and verifying JWTs
  4. Securing endpoints using JWTs
  5. Testing it all with real requests

Let’s dive in.

What is JWT and Why Use It?

JWT (JSON Web Token) is an open standard for securely transmitting information as a JSON object. It’s compact, URL-safe, and digitally signed using a secret or RSA key.

A typical JWT has three parts:

header.payload.signature

Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.   
eyJ1c2VyX2lkIjoxMjMsImVtYWlsIjoiam9obi5kb2VAZ21haWwuY29tIn0.   
bZ3jYpVNvhGwXc1nLfEcKh6LVaE8G6h_vSPKsQp0OaY

You can decode the payload without the secret, but the signature ensures it hasn’t been tampered with.

Setting Up FastAPI Project

Let’s start by creating a basic FastAPI project.

mkdir fastapi-jwt-auth 
cd fastapi-jwt-auth 
python -m venv venv 
source venv/bin/activate 
pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt]

Create a file named main.py:

from fastapi import FastAPI 
 
app = FastAPI() 
 
@app.get("/") 
def root(): 
    return {"message": "Welcome to JWT Auth with FastAPI!"}

Run the server:

uvicorn main:app --reload

Creating JWT Tokens

Let’s set up token creation using python-jose.

from jose import JWTError, jwt 
from datetime import datetime, timedelta 
 
SECRET_KEY = "your-super-secret-key" 
ALGORITHM = "HS256" 
ACCESS_TOKEN_EXPIRE_MINUTES = 30 
 
def create_access_token(data: dict): 
    to_encode = data.copy() 
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) 
    to_encode.update({"exp": expire}) 
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

Now, create a login route that returns the JWT.

Example: Login Endpoint

from fastapi import Depends, HTTPException, status, Form 
from fastapi.security import OAuth2PasswordRequestForm 
from passlib.context import CryptContext 
 
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 
 
fake_users_db = { 
    "johndoe": { 
        "username": "johndoe", 
        "hashed_password": pwd_context.hash("secret"), 
    } 
} 
 
def verify_password(plain_password, hashed_password): 
    return pwd_context.verify(plain_password, hashed_password) 
 
def authenticate_user(username: str, password: str): 
    user = fake_users_db.get(username) 
    if not user or not verify_password(password, user["hashed_password"]): 
        return False 
    return user 
 
@app.post("/login") 
def login(form_data: OAuth2PasswordRequestForm = Depends()): 
    user = authenticate_user(form_data.username, form_data.password) 
    if not user: 
        raise HTTPException(status_code=401, detail="Invalid credentials") 
 
    token = create_access_token(data={"sub": user["username"]}) 
    return {"access_token": token, "token_type": "bearer"}

Protecting Routes with JWT

To protect routes, decode and validate the token on every request.

from fastapi.security import OAuth2PasswordBearer 
 
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") 
 
def get_current_user(token: str = Depends(oauth2_scheme)): 
    try: 
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 
        username = payload.get("sub") 
        if username is None: 
            raise HTTPException(status_code=401, detail="Invalid token") 
        return {"username": username} 
    except JWTError: 
        raise HTTPException(status_code=401, detail="Invalid token") 
 
@app.get("/protected") 
def protected_route(current_user: dict = Depends(get_current_user)): 
    return {"message": f"Hello {current_user['username']}, you are authenticated!"}

Now, accessing /protected without a valid JWT will return 401 Unauthorized.

Test It with curl or Postman

  1. Login to get token:
curl -X POST "http://127.0.0.1:8000/login" \ 
     -d "username=johndoe&password=secret" \ 
     -H "Content-Type: application/x-www-form-urlencoded"

2. Access protected route:

curl -H "Authorization: Bearer YOUR_TOKEN" http://127.0.0.1:8000/protected

Pro Tips for Production

  • Always use HTTPS to prevent token interception
  • Rotate your SECRET_KEY periodically
  • Store JWTs in secure HttpOnly cookies for web apps
  • Add refresh tokens for longer sessions
  • Use scopes or roles for fine-grained access control

Wrapping Up

FastAPI and JWT are a match made for modern authentication. Whether you’re building a full-stack web app or a backend for mobile, mastering this combo is essential.

If you found this guide useful, consider following me for more FastAPI tutorials and backend best practices.

Photo by ZHENYU LUO on Unsplash