DEV Community

Secure Your FastAPI App in 3 Lines of Code (Without Breaking Swagger UI)

Setting standard HTTP security headers—such as Content-Security-Policy (CSP), Strict-Transport-Security (HSTS), and X-Frame-Options—is standard practice before shipping an API to production. They protect your users and infrastructure against common web vulnerabilities like clickjacking, MIME-type sniffing, cross-site scripting (XSS), and cross-origin resource leaks.

However, in FastAPI, developers routinely hit a frustrating roadblock the moment they attempt to enforce a strict Content Security Policy:

FastAPI's interactive documentation (/docs and /redoc) breaks immediately.

The interactive docs rely heavily on inline script execution, stylesheets, and remote assets to render Swagger UI and ReDoc properly on page load. When you add a generic, restrictive CSP middleware, the browser enforces the directives strictly, blocks those assets, and leaves you with a blank page. Developers typically end up wasting hours tuning directives by trial and error, or worse, abandoning CSP entirely.

To solve this problem cleanly, I built fastapi-security-headers.


Why This Implementation?

Many existing security header packages for Python web frameworks either wrap requests with high-level Starlette abstractions, buffer full response bodies into memory, or introduce unnecessary third-party dependencies into your dependency tree.

fastapi-security-headers was designed to be as minimal, direct, and efficient as possible:

  • Pure ASGI: Operates directly at the ASGI specification level. It intercepts the http.response.start message and injects headers directly into the stream without buffering payloads or wrapping request/response objects.
  • Pre-encoded Bytes: Headers are processed and encoded into raw tuple[bytes, bytes] structures once during application initialization. At runtime, injecting headers into a response involves virtually zero compute overhead.
  • Zero External Dependencies: Built strictly using Python's standard library (dataclasses, typing). It will never bloat your poetry.lock or requirements.txt.
  • Docs-Aware Presets: Ships with tested presets that enforce strict CSP policies while granting the precise exemptions needed for Swagger UI and ReDoc to work out of the box.
  • Safe for Streams and WebSockets: Automatically avoids interfering with StreamingResponse, Server-Sent Events (SSE), or WebSocket connections.
  • Route-Aware Precedence: Honors explicit headers set by individual route handlers, only filling in missing security policies rather than blindly overwriting endpoint behavior.

Installation

Install the package via pip:

pip install fastapi-security-headers
Enter fullscreen mode Exit fullscreen mode

Quickstart: Securing Your API and Docs

The fastest way to secure an API while keeping the documentation intact is using the built-in swagger_friendly preset:

from fastapi import FastAPI
from fastapi_security_headers import SecurityHeadersMiddleware, Presets

app = FastAPI(
    title="Secure Production API",
    docs_url="/docs",
    redoc_url="/redoc"
)

# Apply baseline security headers + Swagger/ReDoc compatible CSP
app.add_middleware(
    SecurityHeadersMiddleware,
    config=Presets.swagger_friendly()
)

@app.get("/")
async def root():
    return {"status": "healthy", "security": "enforced"}

@app.get("/items/{item_id}")
async def get_item(item_id: int):
    return {"id": item_id, "name": f"Item {item_id}"}
Enter fullscreen mode Exit fullscreen mode

With this single middleware configuration:

  • Every standard API endpoint receives strict transport and isolation headers.
  • Visiting /docs or /redoc loads all necessary scripts, styles, and assets without generating CSP violations in the browser console.

The Default Baseline Headers

When using fastapi-security-headers, every response is protected with the following baseline headers:

HTTP Header Default Value Security Impact
X-Content-Type-Options nosniff MIME-Sniffing: Prevents browsers from guessing content types, stopping executable payloads disguised as non-executable types like images or JSON.
X-Frame-Options DENY Clickjacking: Disallows any domain from rendering your site inside an <iframe>, preventing UI redressing attacks.
X-XSS-Protection 0 Auditor Exploits: Disables legacy browser XSS filters that have been demonstrated to introduce new vulnerabilities (per modern OWASP guidelines).
Strict-Transport-Security max-age=31536000; includeSubDomains SSL Stripping / MitM: Instructs browsers to communicate exclusively over HTTPS for the next 12 months, including all subdomains.
Referrer-Policy strict-origin-when-cross-origin Data Leakage: Sends the full URL only for same-origin requests; strips query strings and paths when navigating across origins.
Permissions-Policy geolocation=(), microphone=(), camera=() Feature Abuse: Explicitly restricts client browsers from invoking unused device hardware APIs.
Cross-Origin-Opener-Policy same-origin Spectre / Process Isolation: Isolates your browsing context from cross-origin windows, preventing malicious document references.
Cross-Origin-Resource-Policy same-origin Cross-Origin Reads: Prevents unauthorized origins from loading your API responses as resources.

Built-In Presets

Different workloads have different requirements. You can choose between pre-configured presets depending on what your service exposes:

1. Swagger-Friendly Preset (Presets.swagger_friendly())

Designed for services that serve interactive documentation. It configures a Content Security Policy that permits assets required by Swagger UI and ReDoc while keeping all other resource vectors locked down:

from fastapi import FastAPI
from fastapi_security_headers import SecurityHeadersMiddleware, Presets

app = FastAPI()

app.add_middleware(
    SecurityHeadersMiddleware,
    config=Presets.swagger_friendly()
)
Enter fullscreen mode Exit fullscreen mode

2. Headless API Preset (Presets.api())

Ideal for pure JSON microservices, internal services, or mobile backends that do not expose any HTML UI:

from fastapi import FastAPI
from fastapi_security_headers import SecurityHeadersMiddleware, Presets

app = FastAPI(docs_url=None, redoc_url=None)

# Restricts default-src to 'none' across all resource types
app.add_middleware(
    SecurityHeadersMiddleware,
    config=Presets.api()
)
Enter fullscreen mode Exit fullscreen mode

This enforces Content-Security-Policy: default-src 'none', disallowing any browser execution of scripts, styles, frames, or plugins.

3. Strict Preset (Presets.strict())

Intended for high-compliance applications (such as financial, enterprise, or healthcare software subject to PCI-DSS, SOC 2, or HIPAA):

from fastapi import FastAPI
from fastapi_security_headers import SecurityHeadersMiddleware, Presets

app = FastAPI()

# Enforces 2-year HSTS with preload flag and strict framing constraints
app.add_middleware(
    SecurityHeadersMiddleware,
    config=Presets.strict()
)
Enter fullscreen mode Exit fullscreen mode

Custom Configuration

If you have specific architectural requirements or need to define custom policies, you can instantiate SecurityConfig directly:

from fastapi import FastAPI
from fastapi_security_headers import SecurityHeadersMiddleware, SecurityConfig

app = FastAPI()

custom_config = SecurityConfig(
    enable_hsts=True,
    hsts_max_age=63072000,          # 2 years
    hsts_include_subdomains=True,
    hsts_preload=True,
    content_security_policy=(
        "default-src 'self'; "
        "img-src 'self' data: https://images.example.com; "
        "script-src 'self'; "
        "frame-ancestors 'none';"
    ),
    custom_headers={
        "X-Permitted-Cross-Domain-Policies": "none",
        "Clear-Site-Data": '"cache", "cookies", "storage"'
    }
)

app.add_middleware(
    SecurityHeadersMiddleware,
    config=custom_config
)
Enter fullscreen mode Exit fullscreen mode

Verifying Headers

You can verify that the headers are properly applied using curl against your local or staging server:

curl -I http://localhost:8000/
Enter fullscreen mode Exit fullscreen mode

Example response:

HTTP/1.1 200 OK
content-type: application/json
x-content-type-options: nosniff
x-frame-options: DENY
x-xss-protection: 0
strict-transport-security: max-age=31536000; includeSubDomains
referrer-policy: strict-origin-when-cross-origin
permissions-policy: geolocation=(), microphone=(), camera=()
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
content-security-policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; img-src 'self' data: https://fastapi.tiangolo.com;
Enter fullscreen mode Exit fullscreen mode

You can also submit your public URL to securityheaders.com to audit your overall compliance grade.


Feedback & Source Code

The library is completely open source under the MIT License.

If you encounter any edge cases with custom documentation routes or want to suggest new presets, feel free to open an issue or start a discussion on GitHub.

Top comments (0)