Skip to content

Fully self-hosted docs

Serve the vendor files from your app and pass their URLs to setup(). script-src then falls back to default-src 'self', with no external origin:

from fastapi.staticfiles import StaticFiles

import fastapi_csp_docs

CSP = (
    "default-src 'self'; "
    "style-src 'self' "
    "'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' "
    "'sha256-QMIg+bpjm3JdElJ388KYke01izlUW0UoNOeKjpMxdgc='; "
    "img-src 'self' data: https://cdn.redoc.ly; "
    "worker-src 'self' blob:"
)

app = FastAPI(docs_url=None, redoc_url=None)
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")


@app.middleware("http")
async def add_csp_header(request: Request, call_next) -> Response:
    response = await call_next(request)
    response.headers["Content-Security-Policy"] = CSP
    return response


fastapi_csp_docs.setup(
    app,
    swagger_js_url="/static/swagger-ui-bundle.js",
    swagger_css_url="/static/swagger-ui.css",
    swagger_favicon_url="/static/favicon.png",
    redoc_js_url="/static/redoc.standalone.js",
    redoc_favicon_url="/static/favicon.png",
    google_fonts_css_url="/static/fonts.css",
)

STATIC_DIR holds these files. examples/static/ has a copy of each, at the versions setup() loads by default:

File setup() parameter
swagger-ui-bundle.js swagger_js_url
swagger-ui.css swagger_css_url
redoc.standalone.js redoc_js_url
favicon.png swagger_favicon_url, redoc_favicon_url
fonts.css and fonts/ google_fonts_css_url

The only external origin left in the policy is https://cdn.redoc.ly in img-src, for ReDoc's logo. The two hashes and worker-src 'self' blob: serve ReDoc's own runtime, wherever its bundle comes from.

URLs under a prefix

setup() puts the request's ASGI root_path in front of an asset URL that starts with a single /. With the app mounted under /api, /static/swagger-ui-bundle.js becomes /api/static/swagger-ui-bundle.js. FastAPI's own docs functions use asset URLs as given.

Absolute (https://...), protocol-relative (//...) and relative URLs are used as given.

The full app is examples/self_hosted_app.py.