middleware.stack

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

One-call secure-by-default middleware stack helper.

secure_stackreturns the secure-by-default middleware list in the contract-passing order —SessionMiddleware -> CSRFMiddleware-> SecurityHeadersMiddleware— so an app author can wire the whole stack in…

One-call secure-by-default middleware stack helper.

secure_stackreturns the secure-by-default middleware list in the contract-passing order —SessionMiddleware -> CSRFMiddleware-> SecurityHeadersMiddleware— so an app author can wire the whole stack in one line without anything being force-injected:

from chirp.middleware.stack import secure_stack

for mw in secure_stack(app.config):
    app.add_middleware(mw)

Passauth=AuthConfig(...) to include AuthMiddlewarein the right place (after sessions, before CSRF) so login auth is part of the same one-line wiring:

for mw in secure_stack(app.config, auth=AuthConfig(load_user=load_user)):
    app.add_middleware(mw)

Passaudit=AuditConfig(level=...) to append an opt-in AuditMiddlewareas the outermost leg (afterSecurityHeadersMiddleware) so it observes the final status code of every audited request. Like the auth leg it is OFF unless you ask for it (AuditConfig defaults to level="none"):

for mw in secure_stack(app.config, audit=AuditConfig(level="metadata")):
    app.add_middleware(mw)

This is deliberately explicit-over-magic: it is a pure list-returning function (the most inspectable/testable shape), not an auto-injecting hook. The app author still callsadd_middlewarefor each piece, and can drop or reorder the list, swap in custom configs, or skip the helper entirely.

The generated stack passes thesecurity_stack and csrf_session order contracts (same classes, same order) and inherits the Wave 2 SessionConfig.secure="auto" posture: the session cookie's Secureflag is resolved at freeze fromconfig.env (Truefor staging/production), not fromconfig.debug. Coupling cookie security to debugis the footgun the env-aware design exists to avoid, so this helper never readsdebug.

middleware.stack

Name Type Default Description
type
qualified_name
element_type
description
source_file
line_number
is_autodoc
autodoc_element
_autodoc_template
_autodoc_url_path
_autodoc_page_type
title
doc_content_hash

Symbols on this page

secure_stack
function
def secure_stack(config: AppConfig, *, auth: AuthConfig | None = None, session: SessionConfig | None = None, csrf: CSRFConfig | None = None, headers: SecurityHeadersConfig | None = None, audit: AuditConfig | None = None, redis_url: str | None = None) -> list[Any]

Return the secure-by-default middleware stack, in contract-passing order.

The returned list is[SessionMiddleware, CSRFMiddleware, SecurityHeadersMiddleware] — exactly the order the security_stackand csrf_sessioncontracts require (Session before CSRF, since the CSRF token lives in the session). Passauth to also include AuthMiddleware, placed after the session leg and before CSRF, giving [SessionMiddleware, AuthMiddleware, CSRFMiddleware, SecurityHeadersMiddleware]. Nothing is force-injected: the caller adds each middleware itself, typically::

for mw in secure_stack(app.config, auth=AuthConfig(load_user=load_user)):
    app.add_middleware(mw)

Defaults are derived from config:

  • The sessionsecret_key is read from config.secret_key.
  • The session cookie'sSecure flag is left as SessionConfig's secure="auto" default. Wave 2 freeze-resolution turns "auto"into True for env in ("staging", "production") and Falseotherwise (notably local development), keyed onconfig.env— never config.debug. So a default-config app ships Securesession cookies in production without the author touching the field, while local dev keeps them non-Secure to avoid logging dev users out over HTTP.

Passsession/csrf/headersto override any leg with your own fully-built config; when omitted, sane defaults are constructed. Pass redis_url to back sessions with RedisSessionStore(requires the redisextra) instead of the default signed-cookie store; it is ignored when an explicitsessionconfig is supplied (that config already owns its store choice).

Parameters

Name Type Default Description
config AppConfig The app config. Supplies ``secret_key`` and (via freeze resolution) the ``env``-derived cookie ``Secure`` posture.
auth AuthConfig | None None Optional ``AuthConfig``. When provided, ``AuthMiddleware(auth)`` is inserted after the session leg and before CSRF (so a CSRF rejection's audit event already carries the resolved ``user_id``). When omitted, no auth leg is added — wire ``AuthMiddleware`` yourself if needed.
session SessionConfig | None None Optional explicit ``SessionConfig``. Overrides the derived default entirely (including ``redis_url``).
csrf CSRFConfig | None None Optional explicit ``CSRFConfig``. Defaults to ``CSRFConfig()``.
headers SecurityHeadersConfig | None None Optional explicit ``SecurityHeadersConfig``. Defaults to ``SecurityHeadersConfig()``.
audit AuditConfig | None None Optional ``AuditConfig``. When provided, ``AuditMiddleware(audit)`` is appended as the **outermost** leg (after ``SecurityHeadersMiddleware``) so it wraps the whole chain and observes the final status code. OFF unless requested — the default ``AuditConfig`` is ``level="none"``. When omitted, no audit leg is added.
redis_url str | None None Optional Redis URL. When set (and ``session`` is omitted), sessions are stored in ``RedisSessionStore`` rather than a signed cookie.

View source · /home/runner/work/chirp/chirp/site/../src/chirp/middleware/stack.py:1