FastAPI β Auth, Middleware & OpenAPI
Secure FastAPI apps with JWT auth, OAuth2, rate limiting middleware, CORS, custom exception handlers, and OpenAPI customisation.
Part 1: What You Will Learn
- Protect routes with OAuth2 bearer tokens.
- Create and verify signed JWT access tokens.
- Add CORS and custom HTTP middleware.
- Understand how authentication affects the generated OpenAPI documentation.
Part 2: Key Concepts
Authentication answers βwho is calling?β, while authorization answers βwhat may this caller do?β. FastAPI integrates OAuth2 security schemes with OpenAPI. JWTs are signed tokens, not encrypted secret storage, so never put passwords or sensitive private data inside the payload.
Part 3: Topic-Specific Code Examples
# Install once: pip install fastapi uvicorn pyjwt python-multipart
from datetime import datetime, timedelta, timezone
from time import perf_counter
import jwt
from fastapi import Depends, FastAPI, HTTPException, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI(title="Secure Demo API")
SECRET_KEY = "replace-this-demo-key-with-a-secret-from-environment"
ALGORITHM = "HS256"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Demo only. Real applications store password hashes in a database.
DEMO_USER = {"username": "student", "password": "learnpython"}
def create_token(username: str) -> str:
payload = {
"sub": username,
"exp": datetime.now(timezone.utc) + timedelta(minutes=30),
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def current_user(token: str = Depends(oauth2_scheme)) -> str:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
if not username:
raise ValueError
return username
except (jwt.InvalidTokenError, ValueError):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
@app.post("/token")
def login(form: OAuth2PasswordRequestForm = Depends()) -> dict[str, str]:
if form.username != DEMO_USER["username"] or form.password != DEMO_USER["password"]:
raise HTTPException(status_code=401, detail="Invalid credentials")
return {"access_token": create_token(form.username), "token_type": "bearer"}
@app.middleware("http")
async def add_process_time(request: Request, call_next):
start = perf_counter()
response = await call_next(request)
response.headers["X-Process-Time"] = f"{perf_counter() - start:.6f}"
return response
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/profile")
def profile(username: str = Depends(current_user)) -> dict[str, str]:
return {"username": username, "message": "Authenticated"}uvicorn auth_app:app --reload # Visit http://127.0.0.1:8000/docs # Click Authorize in /docs and enter username student and password learnpython.
Part 4: How the Example Works
The OAuth2 scheme tells FastAPI and OpenAPI that protected routes require a bearer token. The token contains a subject and expiration time and is signed with a secret key. The middleware measures request processing time and adds it to a response header. The hard-coded password is intentionally limited to a learning demo; real systems must use hashed passwords and secrets loaded from environment variables or a secret manager.
Part 5: Hands-On Practice
Mini project β Role-Protected API. Add a role claim to the JWT. Create an /admin route that returns data only when the decoded role is admin, and return HTTP 403 for authenticated users without permission.
Part 6: Next Steps
Run and modify the examples in Visual Studio 2026, then continue to Lesson 26. Return to Python Tutorial Home to review the complete curriculum.