Skip to content

Middleware

Craft Easy API includes a middleware stack that handles request tracking, domain routing, and error monitoring. Middleware is automatically configured when the application starts.

Request ID

Every HTTP request gets a unique identifier for tracing across logs and services.

How it works:

  1. If the incoming request has an X-Request-ID header, that value is used
  2. Otherwise, a new UUID is generated
  3. The ID is stored in request.state.request_id
  4. The ID is added to the response as an X-Request-ID header
  5. All log entries within the request inherit this ID via structlog contextvars

Usage in code:

@app.get("/my-endpoint")
async def my_endpoint(request: Request):
    request_id = request.state.request_id
    # Use for downstream service calls, log correlation, etc.

Client-side: Pass your own X-Request-ID header to correlate requests across services:

curl -H "X-Request-ID: my-trace-123" https://api.example.com/users

Security Headers

The SecurityHeadersMiddleware adds HTTP security headers recommended by OWASP and securityheaders.com to every response. Enabled by default.

Headers set on every response:

Header Value
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy camera=(), microphone=(), geolocation=()

Conditional headers:

Header Condition
Strict-Transport-Security Only when SECURITY_HEADERS_HSTS=true and the request arrived over HTTPS (or X-Forwarded-Proto: https)
Content-Security-Policy Only when SECURITY_HEADERS_CSP is set

Configuration

Setting Default Description
SECURITY_HEADERS_ENABLED true Enable the security headers middleware
SECURITY_HEADERS_HSTS true Include Strict-Transport-Security header (only applies on HTTPS)
SECURITY_HEADERS_HSTS_PRELOAD false Append ; preload to the HSTS header (for HSTS preload list submission)
SECURITY_HEADERS_CSP None Content-Security-Policy header value (supports {nonce} placeholder)
SECURITY_HEADERS_CSP_NONCE false Generate a per-request nonce and replace {nonce} in the CSP value
SECURITY_HEADERS_X_FRAME_OPTIONS "DENY" Value for X-Frame-Options header
SECURITY_HEADERS_REFERRER_POLICY "strict-origin-when-cross-origin" Value for Referrer-Policy header
SECURITY_HEADERS_PERMISSIONS_POLICY "camera=(), microphone=(), geolocation=()" Value for Permissions-Policy header

HSTS Behavior

HSTS is only sent when the request was made over HTTPS. In local development (plain HTTP), the header is automatically skipped even when SECURITY_HEADERS_HSTS=true. The middleware checks both request.url.scheme and the X-Forwarded-Proto header (for reverse-proxy deployments).

The header value is:

Strict-Transport-Security: max-age=31536000; includeSubDomains

With SECURITY_HEADERS_HSTS_PRELOAD=true:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

The max-age is always one year (31536000 seconds). includeSubDomains is always present.

Content Security Policy

CSP is intentionally left unconfigured by default because it is highly implementation-specific. Set it per application:

# settings.py or environment variable
SECURITY_HEADERS_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"

CSP Nonces for inline scripts and styles

The built-in pages at /docs, /security, and /erd render inline <script> and <style> tags. When SECURITY_HEADERS_CSP_NONCE=true, the middleware generates a cryptographically random nonce for every request and injects it into those tags, allowing you to use a strict CSP without 'unsafe-inline'.

Enable nonces:

SECURITY_HEADERS_CSP_NONCE=true
SECURITY_HEADERS_CSP="default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}'"

The {nonce} placeholder in the CSP value is replaced with the per-request nonce at response time. Each request receives a unique nonce generated via secrets.token_urlsafe(16).

The nonce is also available to route handlers via request.state.csp_nonce for custom pages that render inline content.

Nonces and caching

Because each response carries a unique nonce, responses with SECURITY_HEADERS_CSP_NONCE=true must not be served from a shared HTTP cache. Set Cache-Control: no-store or private on pages that use nonces.

Disabling

To disable all security headers (e.g., for testing):

SECURITY_HEADERS_ENABLED=false

Domain Routing

When WHITE_LABEL_ENABLED=true, the middleware resolves the requesting domain to a specific tenant. This enables multi-tenant white-label deployments where each tenant has their own domain.

The middleware extracts the Host header and resolves it to a tenant_id, which is then used to scope all database queries for that request.

Sentry Integration

Error tracking via Sentry is built in. Configure with these settings:

Setting Default Description
SENTRY_DSN None Sentry DSN (disabled when not set)
SENTRY_ENVIRONMENT "development" Environment tag
SENTRY_TRACES_SAMPLE_RATE 0.1 Percentage of transactions to trace (0.0–1.0)
SENTRY_PROFILES_SAMPLE_RATE 0.1 Percentage of profiled transactions (0.0–1.0)
SENTRY_RELEASE None Release identifier

When a DSN is configured, Sentry automatically captures:

  • Unhandled exceptions with full stack traces
  • Request context (method, path, headers)
  • User context (user_id, tenant_id)
  • Performance traces (request duration, database queries)
# settings.py
SENTRY_DSN = "https://abc123@o456.ingest.sentry.io/789"
SENTRY_ENVIRONMENT = "production"
SENTRY_TRACES_SAMPLE_RATE = 0.2  # 20% of requests