Skip to content

Middleware & Request Context

Kirak adds four middlewares automatically, in this order (outermost/first-to-run to innermost/last-to-run before your route):

  1. Body-size-limit middleware – rejects any request over 10 MB (Content-Length) with a 413, before anything else runs.
  2. SecurityHeadersMiddleware – sets security response headers on every response.
  3. RequestContextMiddleware – tracks every request with a correlation ID, making distributed tracing and log correlation straightforward.
  4. MonitoringMiddleware – a no-op unless the monitoring module is enabled; otherwise records request timing.

RequestContextMiddleware is covered in detail below since it’s the one most application code interacts with directly.


Unnamed internal middleware (_MAX_BODY_BYTES = 10 * 1024 * 1024 in kirak/kirak_instance.py). Always active, not configurable. Returns a JSON 413 error for any request whose Content-Length exceeds 10 MB, before the request reaches routing or parsing.


File: kirak/middleware/security_headers.py

Sets these headers on every response, unconditionally:

Header Value
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection 1; mode=block
Referrer-Policy strict-origin-when-cross-origin

File: kirak/middleware/request_context.py

Added automatically to every Kirak application on startup. No configuration required.

  1. Extracts or generates a request ID – reads X-Request-ID from the incoming request headers. If not present, generates a UUID4.
  2. Stores the request ID in a ContextVar – available throughout the entire async call stack (hooks, operations, background tasks spawned during the request).
  3. Adds X-Request-ID to the response headers – clients can use this for support and debugging.
  4. Cleans up after the request – clears context variables to prevent cross-request leakage.
Header Direction Description
X-Request-ID Request (optional) Client-supplied correlation ID. Kirak uses it as-is if present.
X-Request-ID Response The correlation ID used for this request (client or auto-generated).

Every log line from the runtime includes the request ID and user ID:

2025-08-20 10:30:01 - [550e8400-e29b-41d4-a716-446655440000] - crud - INFO - [user:42] - [FETCH] Model: posts

Every module logs to the same shared kirak.log file, so this ID is enough to trace a single request across every module’s log lines with a simple grep.

The middleware skips its own dispatch for WebSocket connections (scope["type"] == "websocket") and passes them directly to the next handler. Request ID tracking for WebSockets must be set up manually.


Context variables are available anywhere in the async call stack – hooks, operations, custom routes, background tasks:

from kirak.middleware.request_context import (
get_request_id,
get_user_id,
set_user_id,
get_current_user,
set_current_user,
)
# From a hook:
@kirak.on("orders").hook("after_create")
async def after_order_created(result):
request_id = get_request_id()
user_id = get_user_id()
# Use for audit logging, tracing, etc.
return result
# From a custom route:
@app.get("/custom")
async def custom_route(request: Request):
request_id = get_request_id() # already set by middleware
...
Function Description
get_request_id() Get the UUID string for the current request
set_request_id(id) Set request ID (normally done by middleware)
get_user_id() Get the authenticated user’s ID (set after auth dependency runs)
set_user_id(id) Set user ID (set by auth dependency)
get_current_user() Get the full authenticated user dict
set_current_user(user) Set authenticated user dict (for WebSocket and background contexts)
clear_request_context() Clear all context variables (called automatically after each request)

File: kirak/middleware/logging_filter.py

A Python logging filter that injects request_id and user_id into every log record. Applied automatically to every module’s logger, all of which share one RotatingFileHandler writing to kirak.log.

Custom loggers can use it too:

import logging
from kirak.middleware.logging_filter import RequestContextFilter
logger = logging.getLogger("my_custom_module")
logger.addFilter(RequestContextFilter())

Log records without an active request context get - as the placeholder values.

File: kirak/middleware/logging_filter.py

A separate logging filter, also applied to every module’s logger, that masks emails, passwords, tokens, and API keys out of log messages before they reach any handler – including the shared kirak.log file and monitoring’s captured logs table.


Pass additional ASGI middleware via create_kirak_app():

from starlette.middleware.gzip import GZipMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
app = create_kirak_app(
models_path="./models/",
middlewares=[
(GZipMiddleware, {"minimum_size": 1000}),
(TrustedHostMiddleware, {"allowed_hosts": ["api.example.com", "*.example.com"]}),
],
)

Kirak’s four built-in middlewares are registered first, and your middlewares=[...] list is added afterward – since Starlette’s add_middleware() prepends (the most recently added middleware becomes outermost), everything you pass in middlewares ends up outermost, running before Kirak’s own middleware stack and your routes. Within your own list, middleware is applied in the order provided (the first tuple ends up outermost of your set).


When code runs outside an HTTP request context (cron jobs, scheduler tasks, management scripts, seeding), there is no active request and no user, so Kirak operations run as guest. Set an identity first:

from kirak.core.context import set_user_context, reset_user_context
token = set_user_context({"role": "system", "user_id": None, "token": None})
try:
await kirak.create("audit_logs", {"data": {"action": "seed_complete"}})
finally:
reset_user_context(token)

A context set this way is used as-is, without the HTTP auth dependency. The system role still goes through the model’s access block: the operation must list {"role": "system"} (here, audit_logs.access.create). Only field-level security is bypassed. The scheduler worker sets no identity for the jobs it runs – a job that calls Kirak sets one itself. See Who a direct call runs as.