tower-sessions-cookie-store

Crates.iotower-sessions-cookie-store
lib.rstower-sessions-cookie-store
version0.5.2
created_at2025-12-18 20:33:06.848368+00
updated_at2025-12-18 20:44:13.188117+00
descriptionCookie-backed session store for tower-sessions (signed/private cookies via tower-cookies)
homepagehttps://github.com/benwilber/tower-sessions-cookie-store
repositoryhttps://github.com/benwilber/tower-sessions-cookie-store
max_upload_size
id1993414
size128,778
Ben Wilber (benwilber)

documentation

https://docs.rs/tower-sessions-cookie-store

README

tower-sessions-cookie-store

crates.io docs.rs

Cookie-backed session persistence for tower-sessions.

This crate provides CookieSessionManagerLayer, a Tower layer for cookie-backed sessions. It integrates with tower-sessions and works with Axum extractors.

Install

cargo add tower-sessions-cookie-store

Features

  • signed (default): integrity-protected cookies (tamper-evident). Cookie contents are readable by the client. Provided by cookie.
  • private: encrypted + authenticated cookies (confidentiality + integrity). Provided by cookie.
  • key-expansion: enables Key::derive_from() for deriving a cookie Key from a 32-byte master key. Requires signed and/or private. Provided by cookie.
  • dangerous-plaintext: plaintext cookies (no integrity, no confidentiality). Intended only for testing/debugging.

Usage (Axum)

For more usage examples, see the examples/ directory.

Signed cookies (default)

use axum::{routing::get, Router};
use tower_sessions_cookie_store::{CookieSessionConfig, CookieSessionManagerLayer, Key, Session};

async fn handler(session: Session) -> String {
    let n: usize = session.get("n").await.expect("session get succeeds").unwrap_or(0);
    session.insert("n", n + 1).await.expect("session insert succeeds");
    format!("n={n}")
}

let secret_key = Key::generate(); // store this someplace safe
let config = CookieSessionConfig::default()
    .with_secure(false); // set true in production (HTTPS)
let router = Router::new()
    .route("/", get(handler))
    .layer(CookieSessionManagerLayer::signed(secret_key).with_config(config));

Configuration

CookieSessionConfig controls cookie attributes and session persistence behavior:

  • name (default: "session")
  • http_only (default: true)
  • same_site (default: SameSite::Strict)
  • secure (default: true)
  • path (default: "/")
  • domain (default: none)
  • expiry (default: none)
    • Expiry::OnSessionEnd: no Max-Age is set
    • Expiry::OnInactivity(d): expiration is computed from the last time the session was modified (reads do not count). If you need sliding expiry on every request, set always_save = true.
    • Expiry::AtDateTime(t): Max-Age is set from the record expiry (absolute unless changed)
  • always_save (default: false): when true, refreshes cookie/expiry on every request even if the session is not modified
  • max_cookie_bytes (default: 4096): maximum encoded cookie value size
  • clear_on_decode_error (default: true): clears invalid/expired/undecodable cookies

Behavior notes

  • Saves happen after your handler returns. If encoding the cookie fails (e.g., the encoded session exceeds max_cookie_bytes), the layer responds with 500 and does not emit Set-Cookie.
  • When clear_on_decode_error is true, a bad/expired/undecodable incoming cookie is actively cleared so the client stops sending it.

Cookie format

The cookie value encodes the full session record using a versioned, base64url-encoded JSON envelope. The format is an implementation detail and may change between releases.

For testing/debugging, the crate exposes tower_sessions_cookie_store::{encode_record, decode_record}.

Benefits of cookie-backed session storage

  • Stateless: no server-side session store to operate.
  • No per-request database/network I/O to load session state.
  • No backend database/table/indexes/migrations needed for session storage.

Security notes

  • Cookie sessions are bearer tokens. If a cookie is stolen, it can be replayed until it expires.
  • Unlike server-side session stores (e.g. Redis), cookie-backed sessions have no server-side state to revoke. Any previously issued cookie remains valid until it expires, even if the application later rotates the session ID, updates session data, or "logs out" the user by clearing the cookie (the client will stop sending it, but an attacker holding a copy can still replay it until expiry).
  • The dangerous-plaintext feature offers no tamper resistance. A client can trivially edit the cookie to escalate privileges and impersonate other users (including staff/admin). Do not enable or use it in real applications.

FAQ

  • How can I log everyone out at once?
    • Rotate the signing/encryption key. All existing cookies will fail verification and be rejected.
  • How can I revoke one user’s session?
    • You can’t centrally revoke a single cookie-only session. One option: store a version/token on the user record and also in the session; reject the session when they don’t match and force the client to reauthenticate. See the security notes above.
  • Why did I get a 500 when saving a session?
    • The encoded cookie likely exceeded max_cookie_bytes (default 4096) or failed to encode/verify; the layer returns 500 and does not set a cookie.
  • Can I store large objects or lots of data?
    • No. Cookies are limited (~4 KB by default). Use a server-side store if you need larger payloads.
  • Can I use this cross-site?
    • Set SameSite::None and secure = true (required by browsers). It is very unlikely that you actually want to do this. Use caution.
  • Do I need HTTPS in production?
    • Yes; leave secure = true so browsers only send the cookie over HTTPS. Only disable it locally for development.
  • Can I allow JavaScript to read my session cookies?
    • Strongly discouraged. Disabling HttpOnly (via config.with_http_only(false)) lets any injected script read and exfiltrate the bearer token, enabling replay. Only do this if you have a very specific, XSS-hardened reason.

License

MIT. See LICENSE.

Commit count: 0

cargo fmt