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 cookieSecureflag (required whensame_site = None).same_site(...)—Strict(default),Lax, orNone.exempt(prefix)— skip CSRF entirely for a path prefix (e.g. webhook endpoints that authenticate by signature instead).trust_origin(origin)— strictOrigin/Refererallow-list used as a fallback for legacy clients that send neither cookie nor header.bind_to_session(bool)— defaults totrue; 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: nosniffX-Frame-Options: DENYReferrer-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