Flask Middleware
Middleware sits between the web server and your Flask application. It intercepts every request before your view functions run and every response before it leaves the server. Flask provides several hooks for adding middleware-like behavior without writing low-level WSGI code.
What Middleware Does
HTTP Request
│
Middleware (runs first)
│
Flask Router
│
View Function
│
Middleware (runs on response)
│
HTTP Response
Common middleware tasks include logging every request, adding security headers, authenticating API tokens, and measuring response times.
Flask Request Hooks
Flask provides four decorators that act as application-level middleware:
| Decorator | When It Runs |
|---|---|
@app.before_request | Before every view function |
@app.after_request | After every view function (has access to the response) |
@app.teardown_request | After every request, even if an exception occurred |
@app.teardown_appcontext | When the app context is torn down |
before_request: Authenticate API Tokens
@app.before_request
def require_api_token():
if request.path.startswith('/api/'):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token or not is_valid_token(token):
return jsonify({'error': 'Unauthorized'}), 401If the function returns a value, Flask uses it as the response and skips the view function entirely. This is how authentication middleware works.
before_request: Rate Limiting
from time import time
request_counts = {}
@app.before_request
def rate_limit():
ip = request.remote_addr
now = time()
window = 60 # 1 minute
limit = 100 # 100 requests per minute
if ip not in request_counts:
request_counts[ip] = []
# Remove requests outside the window
request_counts[ip] = [t for t in request_counts[ip] if now - t < window]
if len(request_counts[ip]) >= limit:
return jsonify({'error': 'Rate limit exceeded'}), 429
request_counts[ip].append(now)after_request: Adding Security Headers
@app.after_request
def add_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
return responseThe after_request function must receive the response object and return it. Add or modify headers before returning.
after_request: Request Timing Logger
import time
@app.before_request
def start_timer():
request._start_time = time.time()
@app.after_request
def log_request(response):
duration = time.time() - getattr(request, '_start_time', time.time())
app.logger.info(
'%s %s %s %.3fs',
request.method,
request.path,
response.status_code,
duration
)
return responseOutput in the log:
GET /api/products 200 0.023s POST /auth/login 302 0.145s
WSGI Middleware
For lower-level middleware that modifies the raw WSGI request/response, wrap the Flask app's WSGI callable. This is how tools like ProxyFix (for handling reverse proxy headers) work:
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)When Flask sits behind Nginx, the original client IP is in the X-Forwarded-For header. ProxyFix reads that header so request.remote_addr shows the real client IP instead of the Nginx proxy IP.
Blueprint-Level Hooks
Hooks can target a specific blueprint instead of the entire app:
@admin.before_request
def require_admin():
if not current_user.is_admin:
abort(403)This check runs only before admin blueprint routes — not before any other route in the application.
Summary
Flask middleware runs before and after every request using @app.before_request and @app.after_request. Use before_request to check authentication, enforce rate limits, or block malicious traffic. Use after_request to add security headers, log response times, or modify the response body. Use WSGI middleware like ProxyFix for lower-level infrastructure concerns. Scope hooks to a specific blueprint using the blueprint's decorator instead of the app's.
