The Security Guards of Your FastAPI Castle
Imagine your FastAPI app is a magical castle. Visitors (requests) arrive at the gate every second. But wait! You can’t just let everyone walk in without checking them first, right?
That’s where Middleware comes in — they’re like security guards standing at every entrance and exit of your castle!
What is Middleware? (The Security Guard Analogy)
Think of middleware like this:
Visitor arrives → Guard checks them → They enter castle →
They do their thing → Guard checks them again → They leave
Every single request passes through middleware twice:
- On the way IN (before your code runs)
- On the way OUT (after your code runs)
Your First Middleware
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def security_guard(request: Request, call_next):
# Guard checks visitor ENTERING
print("Someone is entering!")
# Let them into the castle
response = await call_next(request)
# Guard checks visitor LEAVING
print("Someone is leaving!")
return response
What happens:
request= the visitor trying to entercall_next(request)= letting them insideresponse= what they’re carrying when they leave
CORS Middleware (The “Who Can Visit” Rule)
Imagine your castle is in Kingdom A. But visitors from Kingdom B want to visit too!
By default, your castle says: “Only people from MY kingdom can enter!”
CORS (Cross-Origin Resource Sharing) is like a special permission slip that says: “It’s okay, let visitors from other kingdoms in too!”
Why Do We Need This?
When your website is at mysite.com and your API is at api.mysite.com:
- The browser thinks they’re from different kingdoms
- Without CORS, the browser blocks the request!
Setting Up CORS
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://mysite.com"],
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["*"],
)
What each setting means:
| Setting | Meaning |
|---|---|
allow_origins |
Which kingdoms can visit |
allow_credentials |
Can they bring cookies? |
allow_methods |
What actions can they do? |
allow_headers |
What extra info can they send? |
Rate Limiting (The “Don’t Overwhelm Us” Rule)
Imagine 1000 visitors trying to enter your castle gate at the SAME SECOND. The guards would be overwhelmed!
Rate Limiting says: “Each person can only knock 10 times per minute. If they knock more, we ignore them.”
Simple Rate Limiter Example
from fastapi import FastAPI, Request, HTTPException
from collections import defaultdict
import time
app = FastAPI()
request_counts = defaultdict(list)
@app.middleware("http")
async def rate_limiter(request: Request, call_next):
client_ip = request.client.host
now = time.time()
# Remove old requests (older than 60 sec)
request_counts[client_ip] = [
t for t in request_counts[client_ip]
if now - t < 60
]
# Check if too many requests
if len(request_counts[client_ip]) >= 10:
raise HTTPException(
status_code=429,
detail="Too many requests!"
)
request_counts[client_ip].append(now)
return await call_next(request)
The magic:
- We track when each visitor last knocked
- If they knocked 10+ times in 60 seconds → BLOCKED!
- Error code
429= “Too Many Requests”
Session Management (Remembering Visitors)
When a visitor enters your castle, you give them a special wristband. Next time they come back, you recognize them!
A session is like that wristband — it helps you remember who someone is across multiple visits.
How Sessions Work
1. Visitor logs in
2. Castle gives them a secret code (session ID)
3. Visitor stores code in their pocket (cookie)
4. Next visit, visitor shows code
5. Castle remembers them!
Simple Session Example
from fastapi import FastAPI, Request, Response
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
# Add session middleware with a secret key
app.add_middleware(
SessionMiddleware,
secret_key="your-super-secret-key"
)
@app.get("/login")
async def login(request: Request):
request.session["user"] = "Alice"
return {"message": "Logged in!"}
@app.get("/profile")
async def profile(request: Request):
user = request.session.get("user", "Guest")
return {"user": user}
Key points:
secret_keyencrypts the session (keep it secret!)request.sessionis like a dictionary per user- Data persists between requests
Custom Middleware (Build Your Own Guard)
Now you’re ready to create your OWN security guards with special powers!
Example: Timing Middleware
Want to know how long each request takes? Build a timer!
from fastapi import FastAPI, Request
import time
app = FastAPI()
@app.middleware("http")
async def add_timing(request: Request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
response.headers["X-Process-Time"] = str(duration)
return response
Example: Authentication Checker
@app.middleware("http")
async def check_auth(request: Request, call_next):
# Skip auth for public paths
if request.url.path in ["/", "/login"]:
return await call_next(request)
# Check for auth token
token = request.headers.get("Authorization")
if not token:
return JSONResponse(
status_code=401,
content={"detail": "Not authenticated"}
)
return await call_next(request)
Request and Response Objects (The Visitor’s Backpack)
Every visitor carries a backpack (Request) with things inside. And they leave with a gift bag (Response) from the castle!
What’s Inside a Request?
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/inspect")
async def inspect_request(request: Request):
return {
"method": request.method, # GET, POST, etc.
"url": str(request.url), # Full URL
"path": request.url.path, # Just the path
"headers": dict(request.headers),
"client_ip": request.client.host,
"cookies": request.cookies,
}
Reading Request Body
@app.post("/data")
async def read_body(request: Request):
# For JSON data
json_data = await request.json()
# For form data
form_data = await request.form()
# For raw bytes
raw_body = await request.body()
return {"received": json_data}
Building a Response
from fastapi.responses import JSONResponse, Response
@app.get("/custom")
async def custom_response():
return JSONResponse(
content={"message": "Hello!"},
status_code=200,
headers={"X-Custom": "My Header"}
)
The Complete Picture
graph TD A[Request Arrives] --> B[Middleware 1: CORS] B --> C[Middleware 2: Rate Limit] C --> D[Middleware 3: Session] D --> E[Middleware 4: Custom] E --> F[Your Route Handler] F --> G[Response Created] G --> H[Middleware 4: Add Headers] H --> I[Middleware 3: Save Session] I --> J[Middleware 2: Log Request] J --> K[Middleware 1: Add CORS Headers] K --> L[Response Sent]
Remember:
- Middleware runs in ORDER going in
- And REVERSE ORDER going out
- Like guards at the entrance — first one in, last one out!
Quick Reference
| Concept | Purpose | One-Liner |
|---|---|---|
| Middleware | Process all requests | Guards at the gate |
| CORS | Allow cross-origin | Permission slips |
| Rate Limiting | Prevent overload | “10 knocks/minute max” |
| Sessions | Remember visitors | Wristbands |
| Custom Middleware | Your special logic | Build your own guard |
| Request | Incoming data | Visitor’s backpack |
| Response | Outgoing data | Gift bag |
You Did It!
You now understand how FastAPI handles every request like a well-guarded castle. Your middleware guards are ready to:
- Check where visitors come from (CORS)
- Prevent overwhelming crowds (Rate Limiting)
- Remember friendly faces (Sessions)
- Do any custom security check you want!
Now go build something awesome!