Adding JWT Authentication to FastAPI with Dependency Injection
Learn how to set up JWT‑based authentication in FastAPI using python‑jose, Pydantic models, and Depends. Follow a step‑by‑step guide that covers token creation, validation, protected routes, error handling, and secret management.
02 Mar 2026, 05:44 UTC

Desired Outcome
By the end of this guide you will have a FastAPI application that issues JSON Web Tokens (JWT) when users authenticate, validates those tokens on protected endpoints, and returns clear error responses when authentication fails. The solution will use python‑jose for JWT handling, Pydantic for data validation, and FastAPI’s Depends system for clean dependency injection.
Prerequisites
- Python 3.10+ installed.
- Virtual environment activated.
- FastAPI and Uvicorn installed:
pip install fastapi uvicorn python-jose[cryptography] pydantic. - An environment variable named
JWT_SECRET_KEYcontaining a 256‑bit (32‑byte) secret. Example:export JWT_SECRET_KEY=$(openssl rand -hex 32). - Optional:
python-dotenvif you prefer a .env file.
Step 1 – Define Pydantic Models
Use Pydantic to describe the data exchanged with the API. The TokenData model represents the JWT payload; UserIn and UserOut model user credentials and public user data.
from pydantic import BaseModel, EmailStr
class TokenData(BaseModel):
username: str
exp: int
class UserIn(BaseModel):
username: str
password: str
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str
Step 2 – Create a JWT Utility Module
The jwt_utils.py module centralises token creation and validation. It reads the secret from the environment and uses HS256. Replace HS256 with RS256 if you prefer asymmetric keys.
import os
import time
from typing import Dict
from jose import jwt, JWTError
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(data: Dict[str, str]) -> str:
to_encode = data.copy()
expire = int(time.time()) + ACCESS_TOKEN_EXPIRE_MINUTES * 60
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_token(token: str) -> TokenData:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return TokenData(**payload)
except JWTError as exc:
raise exc
Step 3 – Build the Authentication Endpoint
The /token route accepts user credentials, verifies them, and returns an access token. In production you would query a database; here we use a hard‑coded user for illustration.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
app = FastAPI()
@app.post("/token", response_model=Dict[str, str])
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# In a real app replace this with DB lookup
if form_data.username != "alice" or form_data.password != "wonderland":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
token = create_access_token({"username": form_data.username})
return {"access_token": token, "token_type": "bearer"}
Step 4 – Create a Security Dependency
Use OAuth2PasswordBearer to parse the Authorization header and a custom dependency to validate the token and fetch the current user. The dependency returns a UserOut instance for downstream routes.
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
async def get_current_user(token: str = Depends(oauth2_scheme)) -> UserOut:
try:
payload = decode_token(token)
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# Simulate user lookup; replace with DB call
if payload.username != "alice":
raise HTTPException(status_code=404, detail="User not found")
return UserOut(
username="alice",
email="[contact removed]",
full_name="Alice Liddell"
)
Step 5 – Protect Routes with the Dependency
Any endpoint that requires authentication simply declares current_user: UserOut = Depends(get_current_user). FastAPI injects the validated user object automatically.
@app.get("/users/me", response_model=UserOut)
async def read_users_me(current_user: UserOut = Depends(get_current_user)):
return current_user
Step 6 – Verify the Implementation
1. Start the server: uvicorn main:app --reload.
2. Request a token:
curl -X POST -d "username=alice&password=wonderland" \
-H "Content-Type: application/x-www-form-urlencoded" \
http://127.0.0.1:8000/token
The response should contain an access_token.
3. Access the protected route:
curl -H "Authorization: Bearer <token>" http://127.0.0.1:8000/users/me
You should receive a 200 OK with the user JSON.
4. Test failure cases: use an expired token or remove the Authorization header. The API must return a 401 Unauthorized with a JSON body containing detail.
Step 7 – Secure Secret Management
Never commit JWT_SECRET_KEY to source control. Store it in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) or use a .env file with python-dotenv during local development. Example .env:
JWT_SECRET_KEY=your-256-bit-secret
Load it in main.py:
from dotenv import load_dotenv
load_dotenv()
Recovery and Rollback
Since this guide only adds read‑only dependencies and a token endpoint, there is no destructive state change. If you need to revoke tokens, implement a token blacklist or change the JWT_SECRET_KEY and rotate all issued tokens. Remember to update clients with the new secret.
Limitations and Future Enhancements
- Current example uses a hard‑coded user; replace with database authentication.
- Token revocation is not handled; consider a Redis blacklist or a short token lifespan.
- No refresh token flow; add a separate endpoint if long‑term sessions are needed.
- HS256 is used for simplicity; switch to RS256 for stronger security and key rotation.
Conclusion
The combination of FastAPI’s Depends, OAuth2PasswordBearer, and python-jose gives you a clean, type‑safe JWT authentication layer. Follow the steps above, adapt the user lookup to your data store, and you’ll have a production‑ready authentication system in minutes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.