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:
- If the incoming request has an
X-Request-IDheader, that value is used - Otherwise, a new UUID is generated
- The ID is stored in
request.state.request_id - The ID is added to the response as an
X-Request-IDheader - 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:
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:
With SECURITY_HEADERS_HSTS_PRELOAD=true:
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):
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)