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:

DecoratorWhen It Runs
@app.before_requestBefore every view function
@app.after_requestAfter every view function (has access to the response)
@app.teardown_requestAfter every request, even if an exception occurred
@app.teardown_appcontextWhen 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'}), 401

If 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 response

The 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 response

Output 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.

Leave a Comment

Your email address will not be published. Required fields are marked *