🐙 tako
Middleware

Security

CSRF protection, security response headers, body-size limits, and cookie sessions for Tako.

tako-rs-plugins bundles the hardening middleware most services need: CSRF protection, a curated set of security response headers, request body limits, and cookie sessions. Each is a builder; configure it and call .into_middleware().

CSRF

csrf::Csrf defaults to the double-submit cookie pattern: a random token is placed in a cookie and must be echoed back in a request header (default x-csrf-token). The middleware verifies the two match. When a Session extension is present, the token is also bound to the active session id, so a token minted under a previous session (for example before privilege rotation) is rejected.

use tako::middleware::IntoMiddleware;
use tako::middleware::csrf::Csrf;
use tako::middleware::session::SameSite;

let csrf = Csrf::new()
  .cookie_name("csrf_token")
  .header_name("x-csrf-token")
  .secure(true)                       // set the cookie Secure flag
  .same_site(SameSite::Strict)        // default
  .exempt("/webhooks")                // bypass CSRF for a path prefix
  .trust_origin("https://app.example.com")
  .into_middleware();

Key knobs:

  • secure(bool) — toggles the cookie Secure flag (required when same_site = None).
  • same_site(...) — Strict (default), Lax, or None.
  • exempt(prefix) — skip CSRF entirely for a path prefix (e.g. webhook endpoints that authenticate by signature instead).
  • trust_origin(origin) — strict Origin / Referer allow-list used as a fallback for legacy clients that send neither cookie nor header.
  • bind_to_session(bool) — defaults to true; bind the token to the session.

Both CSRF and session cookies default to Secure. For local plain HTTP, explicitly call .secure(false).

For shared CSRF state use .store(backend) with CsrfTokenStore, register session middleware first, and configure .single_use(true) for atomic token consumption. .token_ttl(duration) controls expiry. Backend failures return 503.

Sessions

session::SessionMiddleware provides cookie-based sessions over an in-memory MemorySessionStore backend by default. Sessions are keyed by a random cookie value and carry arbitrary serde-compatible data.

use tako::middleware::IntoMiddleware;
use tako::middleware::session::{SameSite, SessionMiddleware, SessionTtl};

let sessions = SessionMiddleware::new()
  .cookie_name("tako_session")        // default
  .ttl(SessionTtl { idle_secs: 3_600, absolute_secs: Some(86_400) })
  .secure(true)
  .http_only(true)                    // default
  .same_site(SameSite::Lax)           // default
  .into_middleware();

SessionTtl separates an idle timeout (default 1 h) from an absolute lifetime cap (default 24 h), so a stolen session id cannot be refreshed forever. A live session re-emits Set-Cookie with a refreshed Max-Age on every request (rolling refresh). Session::rotate swaps the session id while keeping the data — call it after login to defend against fixation. SessionMiddleware::handle() returns a handle with a revoke_all API for emergency purges.

The default store is in-process and not shared across instances. For a clustered deployment, back sessions with a shared store via the SessionMiddleware::new().store(backend) builder and tako::stores::SessionStore.

Security headers

security_headers::SecurityHeaders emits a curated set of response headers following OWASP / MDN guidance. Three are always on; the rest are opt-in.

Always emitted:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin
use tako::middleware::IntoMiddleware;
use tako::middleware::security_headers::SecurityHeaders;

let headers = SecurityHeaders::new()
  .hsts(true)
  .hsts_max_age(31_536_000)
  .hsts_include_subdomains(true)
  .hsts_preload(true)
  .csp("default-src 'self'")
  .frame_options("SAMEORIGIN")
  .coop("same-origin")
  .coep("require-corp")
  .corp("same-origin")
  .permissions_policy("geolocation=(), camera=()")
  .into_middleware();

For inline scripts, csp_with_nonce(template) generates a per-request nonce, exposes it as a CspNonce extension for handlers to interpolate, and substitutes it into the emitted header. csp_report_only(template) emits a report-only policy instead.

X-XSS-Protection is intentionally not emitted — modern browsers ignore it and OWASP recommends removing it. CSP is the authoritative replacement.

Body limit

Buffered extractors already use a 2 MiB default router limit. Configure router.body_limit(bytes) or explicitly call router.disable_body_limit(). The middleware below can add route-dependent limits and early rejection.

body_limit::BodyLimit rejects oversized request bodies before they are read, preventing resource-exhaustion attacks. It fast-rejects using Content-Length when present.

use tako::middleware::IntoMiddleware;
use tako::middleware::body_limit::BodyLimit;

// Static 1 MiB limit.
let limit = BodyLimit::new(1024 * 1024).into_middleware();

// Dynamic limit based on the request.
let dynamic = BodyLimit::with_dynamic_limit(|req| {
  if req.uri().path().starts_with("/upload") {
    50 * 1024 * 1024
  } else {
    1024 * 1024
  }
}).into_middleware();

See the middleware model for chain ordering — register body limit and security headers on the outside of the chain.

Last updated on

On this page