Skip to content

Authentication Middleware — Reusable Auth Logic for API Frameworks

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Authentication Middleware. We cover key concepts, practical examples, and best practices to help you master this topic.

Authentication middleware is a reusable software component that intercepts incoming API requests, validates credentials, and either attaches user context or rejects the request.

What You'll Learn

How to build authentication middleware for different frameworks, handle multiple auth methods, attach user context to requests, and return proper error responses.

Why It Matters

Without middleware, every endpoint repeats authentication logic. This leads to inconsistencies, missing validation on some routes, and difficult maintenance. Middleware centralizes authentication so one change updates all protected endpoints.

Real-World Use

Flask @login_required, Express passport.authenticate(), and Django REST Framework's AuthenticationClasses are all middleware that handle authentication before requests reach route handlers.

flowchart LR
    A["Request"] --> B["Auth Middleware"]
    B --> C{"Has valid auth?"}
    C -->|"Yes — attach user"| D["Route Handler"]
    C -->|"No"| E["401 Response"]
    D --> F["Response"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style D fill:#dcfce7,stroke:#16a34a
    style E fill:#fecaca,stroke:#dc2626

Code Example: Flask Auth Middleware Decorator

from flask import Flask, request, jsonify, g
from functools import wraps
import jwt

app = Flask(__name__)
SECRET = "your-secret"

def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get("Authorization", "")

        if not auth_header.startswith("Bearer "):
            return jsonify({
                "error": "Unauthorized",
                "message": "Missing or invalid Authorization header"
            }), 401

        token = auth_header[7:]

        try:
            payload = jwt.decode(token, SECRET, algorithms=["HS256"])
            g.current_user = payload  # Attach to request context
        except jwt.ExpiredSignatureError:
            return jsonify({
                "error": "Token expired",
                "message": "Please refresh your token"
            }), 401
        except jwt.InvalidTokenError as e:
            return jsonify({
                "error": "Invalid token",
                "detail": str(e)
            }), 401

        return f(*args, **kwargs)
    return decorated

# Usage
@app.route("/api/profile")
@require_auth
def profile():
    return jsonify({
        "user": g.current_user["sub"],
        "role": g.current_user.get("role", "user")
    })

@app.route("/api/public")
def public():
    return jsonify({"message": "This is public"})

Code Example: Express.js Auth Middleware

const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;

function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Missing or invalid Authorization header'
    });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Usage
app.get('/api/profile', authenticate, (req, res) => {
  res.json({ user: req.user.sub, role: req.user.role });
});

Code Example: FastAPI Auth Middleware

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt

app = FastAPI()
security = HTTPBearer(auto_error=False)

async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security)
):
    if not credentials:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing authorization"
        )

    try:
        payload = jwt.decode(
            credentials.credentials, SECRET, algorithms=["HS256"]
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/api/profile")
async def profile(user: dict = Depends(get_current_user)):
    return {"user": user["sub"], "role": user.get("role")}

@app.get("/api/public")
async def public():
    return {"message": "Public endpoint"}

Common Mistakes

1. Not Using @wraps (Flask) or functools.wraps

Without wraps, the decorated function loses its metadata (name, docstring). This breaks route registration and introspection.

2. Silent Authentication Failures

Returning 500 instead of 401 makes debugging impossible. Always return proper HTTP status codes.

3. Not Handling All JWT Error Types

ExpiredSignatureError and InvalidTokenError are different. Return different error messages so clients know whether to refresh or re-login.

4. Leaking Token Data in Error Messages

Including the full token in error messages exposes credentials. Truncate or hash token values in logs and errors.

5. Applying Middleware to Public Routes

Public routes should bypass authentication. Use route-level decorators or exclude paths in the middleware configuration.

Practice Questions

  1. What is the purpose of authentication middleware?
  2. How does g work in Flask middleware?
  3. Why should middleware return 401 instead of 403 for missing auth?
  4. How does Express.js pass user data from middleware to route handlers?
  5. What is the difference between middleware and route-level auth?

Answers:

  1. Middleware intercepts requests before route handlers, validates authentication, and attaches user context — centralizing auth logic.
  2. Flask's g object stores data for the current request. Middleware attaches user info to g, and route handlers access it via g.current_user.
  3. 401 means "authentication required or failed." 403 means "authenticated but not authorized." Use 401 for missing/invalid credentials.
  4. By attaching data to the req object (req.user = decoded). Route handlers access it via req.user.
  5. Middleware applies to all routes or a group. Route-level auth applies to specific endpoints. Use middleware for general auth, route-level for permissions.

Challenge: Build a Flask auth middleware that supports multiple auth methods (Bearer JWT, Basic Auth, API Key) and attaches the appropriate user context. Include proper error responses for each failure case.

FAQ

Should middleware validate the token on every request?

Yes. Token validation is fast (JWT signature verification takes microseconds). Stateless validation allows horizontal scaling.

Can middleware modify the request body?

Avoid it. Middleware should read headers, validate auth, and attach user context. Modifying the body creates confusion.

How do I test auth middleware?

Use test requests with valid tokens, expired tokens, missing headers, and malformed headers. Assert the correct status code and error message.

Should middleware handle refresh tokens?

No. Middleware validates access tokens. Refresh logic belongs in a separate endpoint or middleware.

Can middleware be used with WebSocket connections?

Yes, but WebSockets do not have standard HTTP headers after upgrade. Authenticate WebSocket connections during the upgrade handshake.

Mini Project

Build a Flask application that uses authentication middleware to protect admin and user routes. Include a public health endpoint, user endpoints (Bearer JWT required), and admin endpoints (JWT with admin role required). Test each with curl.

What's Next

Now complete the API Authentication Project — a capstone that combines everything you have learned into a working authentication system.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro