JWT Authentication in FastAPI — The Complete Guide for Secure APIs
Learn how to implement JSON Web Token (JWT) authentication in FastAPI the right way.

🔐 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:
- What JWT is and how it works
- Setting up FastAPI with authentication dependencies
- Creating and verifying JWTs
- Securing endpoints using JWTs
- 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
- 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.
