Middleware¶
fastapi-fullauth ships three middleware classes. None of them are wired automatically; init_app() only mounts routers. Import what you want and call app.add_middleware(...) yourself.
Middleware order¶
FastAPI applies middleware in reverse registration order. Register the broadest protections first (they execute outermost), then narrow ones, then init_app:
from fastapi_fullauth.middleware import (
SecurityHeadersMiddleware,
CSRFMiddleware,
RateLimitMiddleware,
)
# 1. Security headers (outermost, runs on every response)
app.add_middleware(SecurityHeadersMiddleware)
# 2. Rate limiting (before auth routes)
app.add_middleware(RateLimitMiddleware, max_requests=60, window_seconds=60)
# 3. CSRF protection (before auth routes, if using cookie auth)
app.add_middleware(CSRFMiddleware, secret=config.SECRET_KEY)
# 4. Auth routers (innermost)
fullauth.init_app(app)
When to use which middleware¶
| Middleware | Use when |
|---|---|
SecurityHeadersMiddleware |
Always. No reason not to. |
CSRFMiddleware |
Using cookie-based auth (CookieBackend). Not needed for bearer-only SPAs. |
RateLimitMiddleware |
No CDN/WAF in front of your service, or you want app-level rate limiting. |
Note
CSRF protection is only needed for cookie-based auth. Cookies are sent automatically by the browser on every request, which is what makes CSRF attacks possible. Bearer tokens in the Authorization header must be explicitly attached by JavaScript, so they're not vulnerable to CSRF.
Security Headers¶
Adds standard security headers to every response:
| Header | Default value | Purpose |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevents MIME-type sniffing |
X-Frame-Options |
DENY |
Prevents clickjacking via iframes |
X-XSS-Protection |
0 |
Disables the deprecated legacy XSS auditor (rely on a CSP) |
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Forces HTTPS for 1 year — sent only over HTTPS |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls referrer information |
Permissions-Policy |
geolocation=(), camera=(), microphone=() |
Disables browser APIs |
HSTS is HTTPS-only
Strict-Transport-Security is emitted only when the request is HTTPS — directly, or via an X-Forwarded-Proto: https header from a trusted proxy. This prevents a stray plaintext-HTTP deploy from pinning HSTS (with includeSubDomains) across sibling subdomains. Disable it entirely with SecurityHeadersMiddleware(app, hsts=False), or change the policy with hsts_value=....
X-XSS-Protection is 0 on purpose
The legacy auditor this header enabled is deprecated, and 1; mode=block is known to introduce cross-site leak oracles in browsers that still honour it. The modern guidance is to disable it and rely on a Content-Security-Policy.
Custom headers¶
Override or add headers:
app.add_middleware(
SecurityHeadersMiddleware,
custom_headers={
"X-Frame-Options": "SAMEORIGIN", # override default
"Content-Security-Policy": "default-src 'self'", # add new
},
)
Tip
Content-Security-Policy is not included by default because it varies heavily by application. Add it via custom_headers with a policy that matches your frontend setup.
CSRF Protection¶
Uses the double-submit cookie pattern:
- On safe requests (
GET,HEAD,OPTIONS), a signed CSRF cookie (fullauth_csrf) is set - On state-changing requests (
POST,PUT,PATCH,DELETE), the client must send the cookie value in theX-CSRF-Tokenheader - The middleware verifies the HMAC-SHA256 signature on the cookie and compares the cookie value against the header
Setup¶
app.add_middleware(
CSRFMiddleware,
secret=config.SECRET_KEY,
cookie_secure=True,
cookie_samesite="lax",
cookie_domain=None,
)
The secret must be at least 32 characters. Pass config.SECRET_KEY, or your own dedicated key if you want to rotate it independently. Match the cookie attributes to whatever you pass your CookieBackend.
Origin allow-list (recommended)¶
The signed double-submit token proves the cookie was server-issued but is not bound to the user's session, so a party able to write a cookie for your domain (a sibling-subdomain takeover, or a MITM on a plaintext sibling host) could plant a matching cookie+header pair. Add trusted_origins to also require a matching Origin/Referer on state-changing requests — the recommended defence in depth for cookie-based auth:
app.add_middleware(
CSRFMiddleware,
secret=config.SECRET_KEY,
trusted_origins=["https://app.example.com"],
)
When set, a state-changing request whose Origin (or Referer) is not in the list is rejected even if it carries a valid token. Requests with no Origin/Referer (non-browser clients) fall back to the token check, so server-to-server callers are unaffected.
Frontend integration¶
Your frontend must read the CSRF cookie and send it as a header:
const csrfToken = document.cookie
.split('; ')
.find(row => row.startsWith('fullauth_csrf='))
?.split('=')[1];
fetch('/api/v1/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
credentials: 'include',
body: JSON.stringify({ email, password }),
});
Exempt paths¶
Skip CSRF for specific paths (e.g. webhooks that receive external POST requests):
app.add_middleware(
CSRFMiddleware,
secret="your-32-plus-character-secret-here",
exempt_paths=["/api/v1/webhooks"],
)
Exempt paths are matched on path-segment boundaries: "/api/v1/webhooks" exempts that exact path and anything under "/api/v1/webhooks/", but not a sibling like "/api/v1/webhooks-admin".
Rate Limiting¶
See Rate Limiting for the full guide on both auth rate limits and the global middleware.