Skip to content

blackbull.middleware.session

blackbull.middleware.session

Signed-cookie session middleware (HMAC-SHA256).

The session payload lives entirely in the cookie sent to the client; the server keeps no per-session state. Every value the client could otherwise tamper with is HMAC-signed by a secret the server alone knows, so:

  • a client that modifies the cookie sees the next request treated as a fresh empty session (signature check fails),
  • the server never reads, writes, or replays anything else — no DB hit, no Redis round-trip, no in-process dict to keep consistent across workers,
  • sessions survive worker restarts / horizontal scaling for free (any worker that knows the secret can validate any cookie).

The trade-offs compared with server-side session stores:

  • Cookie size is bounded (browsers cap to ~4 KiB, often less in practice). Sessions storing large objects don't fit.
  • No server-side invalidation: revoking a cookie before it expires requires either rotating the secret (kills every session) or a separate revocation list. Acceptable for many apps.

Wire format (Base64URL-encoded payload + "." + hex MAC)::

<urlsafe-b64-no-padding(json-bytes)>.<hex-hmac-sha256(json-bytes)>

The cookie is dispatched on every response that touched scope['session'] in a way that modified it (see :class:_SessionDict); unmodified sessions trigger no Set-Cookie header, so a 304 / cache-friendly response stays cache-friendly.

Usage::

from blackbull.middleware import Session

app.use(Session())                       # reads BB_SESSION_SECRET
app.use(Session(secret=b'\x...'))       # explicit secret

@app.route(path='/')
async def index(scope, receive, send):
    scope['session']['user'] = 'alice'             # writes Set-Cookie
    await send({'type': 'http.response.start', ...})
    await send({'type': 'http.response.body', ...})

Session

ASGI middleware that maintains a signed-cookie session.

Parameters

secret: The HMAC key. Bytes or str. When omitted, BB_SESSION_SECRET is read from the environment. A missing / empty secret raises at construction — no insecure default. cookie_name: Name of the cookie carrying the session payload. Default 'session'. max_age: Cookie Max-Age in seconds. When set, the cookie is signed with a server-side timestamp; values older than max_age seconds are treated as expired (empty session). None means a session cookie that lives only as long as the browser is open. secure: Set the Secure attribute so the cookie is only sent over HTTPS. Default True; set False for local-only dev. httponly: Set the HttpOnly attribute (JavaScript can't read the cookie). Default True. samesite: Strict / Lax / None. Default 'Lax'. path: Cookie Path. Default /.

binascii_error_class()

Return binascii.Error lazily — avoids a top-level import.