Building Secure APIs with FastAPI and JWT
What is JWT?
JSON Web Token (JWT) is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. It is compact, self-contained, and digitally signed, meaning that it can be trusted and verified.
A typical JWT consists of three parts:
Header – Specifies the signing algorithm.
Payload – Contains the claims (data).
Signature – Verifies that the token hasn’t been tampered with.
Example token format:
xxxxx.yyyyy.zzzzz
These tokens are widely used in authentication systems because they enable stateless communication between clients and servers, meaning the server doesn’t need to store session info — the token carries all the necessary data.
Setting Up FastAPI for JWT Auth
First, let’s install the required dependencies:
pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipartThese packages enable FastAPI, handle password hashing, JWT encoding/decoding, and parsing form data.
JWT Authentication Setup
Step 1: Basic Configuration
Start by importing required libraries and setting up constants:
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
# JWT Configuration
SECRET_KEY = "your-secret-key" # Use a secure key in production
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30Step 2: Password Hashing and OAuth2 Setup
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2 Bearer Token URL
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Hash a password
def get_password_hash(password):
return pwd_context.hash(password)
# Verify password
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)Step 3: Creating & Verifying JWT Tokens
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
return {"username": username} # In real apps, fetch from DBLogin Endpoint
Let’s allow users to log in and receive JWT tokens.
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel
app = FastAPI()
# Mock database
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # 'secret'
"disabled": False,
}
}
class Token(BaseModel):
access_token: str
token_type: str
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or not verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"]},
expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}Protecting Routes with JWT
Now let’s add some routes that require authentication.
class User(BaseModel):
username: str
email: str
full_name: str
disabled: Optional[bool] = None
@app.get("/users/me", response_model=User)
async def read_users_me(current_user: dict = Depends(get_current_user)):
user = fake_users_db.get(current_user["username"])
return user
@app.get("/protected-resource")
async def protected_resource(current_user: dict = Depends(get_current_user)):
return {"message": "This is a protected resource", "user": current_user}Conclusion
With this setup, you've created a full JWT authentication flow using FastAPI. This approach is:
✅ Stateless — no server-side session storage required.
✅ Scalable — easy to deploy on distributed systems.
✅ Secure — uses industry-standard cryptographic algorithms.
Final Tips
Always use HTTPS in production to prevent token interception.
Store your
SECRET_KEYsecurely — ideally in environment variables or a secrets manager.Consider implementing:
Refresh tokens for longer sessions
Role-based access control (RBAC)
Integration with OAuth providers like Google, GitHub, etc.
Now you're ready to build secure APIs with FastAPI and JWT authentication. Happy coding!
Sagar Kundu
Software Engineer & Technical Writer
Sagar Kundu shares knowledge and experiences through articles and open-source contributions.