Cookie-backed session persistence for tower-sessions.
  • Rust 99.6%
  • Just 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
cnidariaware 01b91d5a28
All checks were successful
CI / CI (stable) (push) Successful in 12m24s
CI / CI (nightly) (push) Successful in 13m53s
chore(ci): added more to the update disclaimer
2026-10-04 18:22:47 -06:00
.cargo chore(cargo): added missing config for registry 2026-10-01 15:33:11 -06:00
.forgejo/workflows chore(ci): now works with perosnal forgejo instance and added another install method 2026-10-04 18:21:05 -06:00
.github/workflows revert(dep): reverted dependencies to hard lock tower-sessions crate in case the interface changes again 2026-10-04 18:06:11 -06:00
examples fix(cate name): now uses updated crate name rather than old name 2026-10-01 15:04:04 -06:00
src fix(cate name): now uses updated crate name rather than old name 2026-10-01 15:04:04 -06:00
tests fix(cate name): now uses updated crate name rather than old name 2026-10-01 15:04:04 -06:00
.gitignore Initial cookie-backed session layer 2025-12-17 10:24:43 -05:00
Cargo.lock chore(version): added missed version upgrade 2026-10-01 15:09:54 -06:00
Cargo.toml revert(dep): reverted dependencies to hard lock tower-sessions crate in case the interface changes again 2026-10-04 18:06:11 -06:00
CHANGELOG.md feat(docs): updated to include changelog fixes and readme reordering 2026-10-04 18:14:38 -06:00
justfile chore: build docs with all features 2025-12-18 13:05:50 -05:00
LICENSE docs: add README and MIT license 2025-12-17 13:37:12 -05:00
README.md chore(ci): added more to the update disclaimer 2026-10-04 18:22:47 -06:00

Disclaimers

This is a fork of the original tower-sessions-cookie-store.

The maintainer has since decided to no longer maintain this crate so I have decided to maintain and update this crate.

Why is it Named like this?

Because cookies are stored in a cookie jar.

Updates

Updates will come earlier to my own personal Forgejo instance as I can delete and fix bugs cleanly without switching versions constantly, but it should be considered unstable. Only the latest crate will be published there, use crates.io for a more stable experience.

Installation Setup this registry in the Cargo configuration file (for example ~/.cargo/config.toml):

[registries.forgejo]
index = "sparse+https://forge.cnidariaware.ca/api/packages/cnidariaware/cargo/" # Sparse index
# index = "https://forge.cnidariaware.ca/cnidariaware/_cargo-index.git" # Git

[net]
git-fetch-with-cli = true

To install the package using Cargo, run the following command:

cargo add tower-sessions-cookie-jar@0.5.3 --registry forgejo

tower-sessions-cookie-jar

No crates.io site right now https://github.com/cnidariaware/tower-sessions-cookie-jar, official homepage for now

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-jar

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_jar::{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.

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_jar::{encode_record, decode_record}.

  • 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.