1.
Introduction to JWT
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-
contained way for securely transmitting information between parties as a JSON object. This
information can be verified and trusted because it is digitally signed.
Why use JWT in FastAPI?
• Statelessness: No need to store sessions on the server side.
• Scalability: Perfect for microservices and distributed systems.
• Performance: Fast verification without database lookups for every request.
Key Benefit: In a FastAPI environment, JWTs allow for highly efficient horizontal
scaling as any instance of your application can verify a token without shared session
storage.
Page 2
2. Anatomy of a JWT
A JWT consists of three parts separated by dots (.).
[Link]
1. Header
Contains the type of token (JWT) and the signing algorithm being used (e.g., HS256).
2. Payload
Contains the claims. Claims are statements about an entity (typically, the user) and additional data.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
3. Signature
To create the signature part you have to take the encoded header, the encoded payload, a secret,
and the algorithm specified in the header.
Page 3
3. Authentication Flow (Visualized)
The following sequence represents the handshake between the Client, API, and Database.
Client
Sends Credentials (Email/Password)
FastAPI Server
Validates Credentials against Database
FastAPI Server
Generates JWT with Secret Key
Client
Receives JWT & Stores in Local Storage/Cookies
Protected Request
Client sends JWT in 'Authorization' Header
Page 4
4. Environment Setup
To implement JWT in FastAPI, we need several core libraries for handling the web framework,
security, and token generation.
# Install required packages
pip install fastapi "uvicorn[standard]"
pip install "python-jose[cryptography]"
pip install "passlib[bcrypt]"
pip install python-multipart
pip install sqlalchemy
Project Structure Recommendation
app/
├── [Link] # Entry point
├── core/
│ ├── [Link] # Environment variables
│ └── [Link] # JWT & Password logic
├── models/ # Database models
├── schemas/ # Pydantic models
└── routers/ # API endpoints
Page 5
5. Core Configuration
Handling secrets and settings using Pydantic Settings is the production-standard approach.
# core/[Link]
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
SECRET_KEY: str = "YOUR_SUPER_SECRET_KEY"
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
class Config:
env_file = ".env"
settings = Settings()
Security Tip: Never commit your .env file to Git. Use a strong 32-byte or 64-byte random string for
the SECRET_KEY.
Page 6
6. Password Hashing Logic
We use Passlib with the Bcrypt backend to ensure passwords are never stored in plain text.
# core/[Link]
from [Link] import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
How it works: Salt is automatically generated and prepended to the hash by Bcrypt,
preventing rainbow table attacks.
Page 7
7. Token Generation (JWT)
This function creates the encoded string that becomes the user's passport to protected routes.
from datetime import datetime, timedelta, timezone
from jose import jwt
from .config import settings
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = [Link]()
if expires_delta:
expire = [Link]([Link]) + expires_delta
else:
expire = [Link]([Link]) + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = [Link](
to_encode,
settings.SECRET_KEY,
algorithm=[Link]
)
return encoded_jwt
Page 8
8. Database Layer
Using SQLAlchemy to define the User entity.
# models/[Link]
from sqlalchemy import Column, Integer, String, Boolean
from [Link] import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
is_active = Column(Boolean, default=True)
Page 9
9. Pydantic Schemas
Schemas define the structure of data sent to and from the API.
# schemas/[Link]
from pydantic import BaseModel, EmailStr
class UserBase(BaseModel):
email: EmailStr
class UserCreate(UserBase):
password: str
class UserOut(UserBase):
id: int
is_active: bool
class Config:
from_attributes = True
class Token(BaseModel):
access_token: str
token_type: str
Page 10
10. Authentication Dependency
This is the engine that protects your routes. It extracts the token, validates it, and fetches the user.
# core/[Link]
from fastapi import Depends, HTTPException, status
from [Link] import OAuth2PasswordBearer
from jose import JWTError, jwt
from .config import settings
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
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 = [Link](token, settings.SECRET_KEY,
algorithms=[[Link]])
email: str = [Link]("sub")
if email is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# Fetch user from DB here
return user
Page 11
11. Implementing Protected Routes
Once the dependency is created, protecting a route is as simple as adding a parameter.
@[Link]("/users/me", response_model=UserOut)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
Security Note: The Depends(get_current_user) ensures that any request to
this endpoint MUST include a valid, unexpired JWT in the header.
Page 12
12. The Login Endpoint
Handling the POST request to generate the token.
from [Link] import OAuth2PasswordRequestForm
@[Link]("/token", response_model=Token)
async def login_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends()
):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=401, detail="Incorrect login")
access_token = create_access_token(data={"sub": [Link]})
return {"access_token": access_token, "token_type": "bearer"}
Page 13
13. Advanced: Refresh Tokens
Access tokens should be short-lived (e.g., 15 mins). Refresh tokens allow users to stay logged in
without re-entering credentials.
Feature Access Token Refresh Token
Lifespan Short (15-60 min) Long (7-30 days)
Storage Memory / JS State HttpOnly Cookie
Purpose API Authorization Getting new Access Token
Page 14
14. Refresh Token Logic
def create_refresh_token(data: dict):
expire = [Link]() + timedelta(days=7)
to_encode = [Link]()
to_encode.update({"exp": expire, "type": "refresh"})
return [Link](to_encode, settings.SECRET_KEY, algorithm=[Link])
@[Link]("/refresh")
async def refresh_token(old_token: str):
# 1. Validate old_token
# 2. Check if type is 'refresh'
# 3. Issue new access_token
return {"access_token": new_access_token}
Page 15
15. Token Revocation & Logout
Since JWTs are stateless, you cannot "delete" them. You must "blacklist" them until they expire.
Strategy: Use a fast in-memory store like Redis to store the JTI (JWT ID) of logged-
out tokens. Check this list in your get_current_user dependency.
# Pseudo-code for blacklist check
jti = [Link]("jti")
if [Link](f"blacklist:{jti}"):
raise HTTPException(status_code=401, detail="Token revoked")
Page 16
16. Model Context Protocol (MCP) in Auth
MCP is a modern standard for connecting AI models to data sources and tools. In the context of
FastAPI Auth, MCP can be used to:
• Automated Testing: Provide the AI with the context of your authentication schemas.
• Dynamic Documentation: Let AI generate client-side auth handlers based on your
FastAPI security dependencies.
• Context Sharing: Securely share session context between different AI-driven
microservices.
Page 17
17. CORS & Production Security
Cross-Origin Resource Sharing (CORS) is vital for frontend integration.
from [Link] import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["[Link]
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Security Checklist
• Use HTTPS only.
• Set HttpOnly and Secure flags on cookies.
• Implement Rate Limiting for login endpoints.
Page 18
18. Testing Auth with Pytest
from [Link] import TestClient
from [Link] import app
client = TestClient(app)
def test_login():
response = [Link]("/token", data={"username": "test@[Link]", "password":
"pwd"})
assert response.status_code == 200
assert "access_token" in [Link]()
def test_protected_route_without_token():
response = [Link]("/users/me")
assert response.status_code == 401
Page 19
19. Common Implementation Errors
Error Cause Resolution
Missing 'Bearer ' Ensure header is 'Authorization: Bearer
401 Unauthorized
prefix [token]'
Signature Verification
Wrong Secret Key Check SECRET_KEY env variable
Failed
422 Unprocessable Entity Wrong Data Format Check Pydantic schema validation
Page 20
20. Best Practices Summary
1. Short-lived tokens: Max 30 minutes for access tokens.
2. Encryption: Use RS256 (Asymmetric) for high-security environments.
3. Validation: Always validate the 'aud' (Audience) and 'iss' (Issuer) claims.
4. Logs: Never log the actual JWT or the Secret Key.
Page 21
21. Conclusion
Implementing JWT in FastAPI provides a robust, scalable, and professional way to handle user
identity. By combining the speed of FastAPI with the security of modern standards like Bcrypt and
OAuth2, you can build production-ready applications capable of handling thousands of concurrent
users.
Master Cheat Sheet: Software Testing & JWT
For further exploration, integrate this with automated testing pipelines and CI/CD security scanning.
Page 22