Frontend Labs
⋯

Day 82 — Middleware Ordering & Pipeline Design

Learning Objectives

  • Understand why middleware order is a security architecture decision
  • Build a configurable middleware pipeline with load-from-JSON ordering
  • Log which middleware rejected each request (for incident response)

Correct Middleware Order (Security-First)

Request flow (each layer can short-circuit):
  1. PanicRecoverer          — outermost: catch panics from ALL other middleware
  2. RequestID               — inject before any logging
  3. CORS Preflight          — handle before auth (CORS is pre-auth by design)
  4. Global Connection Limit — protect server resources
  5. Per-IP Rate Limit       — DDoS mitigation before auth (before you know who it is)
  6. TLS/mTLS Check          — verify transport security
  7. Bot/Scanner Check       — reject known scanners early
  8. Authentication          — verify identity
  9. Per-User Rate Limit     — quota enforcement (after you know who)
 10. WAF                     — inspect payload (after auth so identity appears in WAF logs)
 11. JSON Schema Validation  — structural validation
 12. Input Validation        — semantic validation
 13. Security Headers        — always set on responses
 14. PII Scrubber            — mask before logging
 15. Request Logger          — log after all decisions made (includes final status)
 16. Application Handler

Why Order Matters

Wrong order: WAF before Auth
  Problem: WAF blocks legitimate traffic before user can authenticate
  Worse:   WAF logs don't include user identity → harder to investigate false positives

Wrong order: Rate limit after Auth
  Problem: Unauthenticated requests can exhaust rate limit budget for real users
  Attack:  Attacker sends 10,000 unauthenticated requests → legitimate users get 429

Wrong order: Security headers inside handler
  Problem: Panicked handler never sets security headers → XSS unprotected

Correct:
  IP rate limit → Auth → User rate limit
  WAF after Auth → WAF logs include user identity
  PanicRecoverer outermost → Security headers always set

Exercise: Configurable Pipeline

package pipeline

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
)

type PipelineConfig struct {
    Middleware []string `json:"middleware"` // names in order
    Required   []string `json:"required"`   // must be present
}

type Registry struct {
    middleware map[string]func(http.Handler) http.Handler
}

func NewRegistry() *Registry { return &Registry{middleware: make(map[string]func(http.Handler) http.Handler)} }

func (r *Registry) Register(name string, mw func(http.Handler) http.Handler) {
    r.middleware[name] = mw
}

func (r *Registry) Build(configFile string, handler http.Handler) (http.Handler, error) {
    data, err := os.ReadFile(configFile)
    if err != nil { return nil, err }

    var cfg PipelineConfig
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("invalid pipeline config: %w", err)
    }

    // Validate required middleware are present
    nameSet := make(map[string]bool)
    for _, name := range cfg.Middleware { nameSet[name] = true }
    for _, req := range cfg.Required {
        if !nameSet[req] {
            return nil, fmt.Errorf("required middleware %q not in pipeline", req)
        }
    }

    // Build chain (reverse order — first in config = outermost)
    chain := handler
    for i := len(cfg.Middleware) - 1; i >= 0; i-- {
        name := cfg.Middleware[i]
        mw, ok := r.middleware[name]
        if !ok {
            return nil, fmt.Errorf("unknown middleware: %q", name)
        }
        inner := chain
        mwName := name
        chain = mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Track which middleware handles each request
            ctx := context.WithValue(r.Context(), pipelineStageKey, mwName)
            inner.ServeHTTP(w, r.WithContext(ctx))
        }))
    }

    return chain, nil
}

pipeline-config.json:

{
  "middleware": [
    "panic_recoverer",
    "request_id",
    "cors",
    "ip_rate_limit",
    "auth",
    "user_rate_limit",
    "waf",
    "schema_validation",
    "security_headers",
    "pii_scrubber",
    "request_logger"
  ],
  "required": [
    "panic_recoverer",
    "auth",
    "request_logger"
  ]
}

Generated from week12.md · 8 Oct 2026 16:36