Man In The Middle in Fastapi with Jwt Tokens
Man In The Middle in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
A Man In The Middle (MitM) scenario in FastAPI with JWT tokens occurs when an attacker intercepts or alters traffic between a client and the API. Even when endpoints require JWTs, failure to enforce transport integrity and strict token validation creates opportunities to read, modify, or inject credentials or sensitive data in-flight.
Without transport-layer protections, JWTs can be captured or tampered with. For example, if an endpoint accepts Authorization: Bearer
JWT-specific risks in FastAPI also arise from how tokens are validated. If the API does not verify the issuer (iss), audience (aud), and expected scopes, an attacker can substitute a token issued for one context to another endpoint. Insufficient signature verification or accepting unsigned tokens (alg: none) enables token forgery. Dynamic routing or reverse proxies that alter headers can inadvertently forward tokens to unintended services, widening the attack surface.
Consider a FastAPI service that authenticates with JWTs but uses a broad CORS configuration:
from fastapi import FastAPI, Depends, HTTPException, Header
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
app = FastAPI()
security = HTTPBearer()
def decode_token(token: str):
# Insecure: no audience/issuer validation, no algorithm restriction
return jwt.decode(token, "secret", algorithms=["HS256", "HS512", "none"])
@app.get("/profile")
def profile(credentials: HTTPAuthorizationCredentials = Depends(security)):
payload = decode_token(credentials.credentials)
return {"user": payload.get("sub")}
In a MitM context, an attacker on the same network can capture the token if TLS is weak or misconfigured. If the service also accepts the none algorithm, an attacker can forge a token and escalate privileges. Headers may be rewritten by proxies, causing the service to trust a token that never reached the intended backend.
Another scenario involves token leakage in logs or error messages. Verbose errors that echo the Authorization header or JWT payload can expose sensitive claims over HTTP before enforcement is applied. In clustered deployments, inconsistent signing keys across instances can allow an attacker who compromises one node to generate valid tokens accepted elsewhere.
MitM with JWTs is not only about network interception; it also includes software supply chain and dependency risks. Outdated PyJWT or FastAPI versions may introduce parsing bugs that affect token validation. Misconfigured HTTPS redirects can strip or downgrade TLS, making interception feasible. Therefore, securing the transport, tightening JWT validation, and hardening deployment configurations are all essential to reduce the impact of MitM in FastAPI with JWT tokens.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on enforcing transport security, tightening JWT validation, and minimizing token exposure. Below are concrete code examples for a FastAPI service that address common MitM and JWT weaknesses.
1) Enforce HTTPS and strict TLS settings in production. Use middleware to reject HTTP requests and ensure host verification.
from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)
@app.middleware("http")
async def verify_host_middleware(request: Request, call_next):
# Reject requests to unexpected hosts to prevent host header attacks
if request.url.host not in {"api.example.com", "www.example.com"}:
raise HTTPException(status_code=400, detail="Host not allowed")
response = await call_next(request)
return response
2) Validate JWTs with audience, issuer, leeway, and algorithm restrictions. Never accept unsigned tokens.
import jwt
from datetime import datetime, timezone
from fastapi import FastAPI, Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
app = FastAPI()
security = HTTPBearer()
def decode_token(token: str):
# Explicitly set options and leeway; reject unsigned tokens
return jwt.decode(
token,
key="your-256-bit-secret",
algorithms=["HS256"],
options={"verify_signature": True, "verify_exp": True, "require": ["exp", "iss", "aud"]},
issuer="example.com",
audience="api.example.com",
leeway=30 # seconds for clock skew
)
@app.get("/profile")
def profile(credentials: HTTPAuthorizationCredentials = Security(security)):
try:
payload = decode_token(credentials.credentials)
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
return {"user": payload.get("sub"), "scopes": payload.get("scopes")}
3) Protect against token replay and logging. Avoid logging Authorization headers and scrub tokens from error traces.
import logging
from fastapi import FastAPI, Request
app = FastAPI()
logger = logging.getLogger("api_logger")
@app.middleware("http")
async def scrub_token_middleware(request: Request, call_next):
# Remove token from logs; do not log Authorization header
auth = request.headers.get("authorization")
if auth:
request.state.scrubbed_auth = "[secure]" if auth.startswith("Bearer ") else auth
else:
request.state.scrubbed_auth = None
response = await call_next(request)
return response
@app.get("/items")
def read_items(request: Request):
# Use scrubbed values if needed for audit trails
return {"endpoint": "/items", "auth_present": bool(request.state.scrubbed_auth)}
4) Use secure cookie attributes and SameSite policies if storing tokens client-side. For APIs, prefer short-lived access tokens with refresh token rotation and binding to client context.
from fastapi import FastAPI, Response
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/login")
def login(response: Response):
response.set_cookie(
key="refresh_token",
value="long-lived-secure-refresh-token",
httponly=True,
secure=True,
samesite="strict",
max_age=86400
)
return JSONResponse(content={"access_token": "short-lived-access-token"})
5) Enforce strict CORS and avoid wildcard origins. Limit methods and headers to what is necessary.
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
expose_headers=["X-Request-ID"],
max_age=86400
)
By combining transport enforcement, strict JWT validation, secure handling practices, and disciplined CORS, the risk of successful MitM against FastAPI services using JWT tokens is substantially reduced.