Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
seshcookie
Stateless, encrypted, typed session cookies for Axum + Tower applications.
seshcookie stores the entire session payload inside a single authenticated cookie - no
database, no Redis, no shared state. A user-supplied secret is stretched to a
ChaCha20-Poly1305 key via HKDF-SHA256, and the sealed cookie includes an authenticated
issued_at timestamp so server-side expiry cannot be forged or extended by the client.
Multi-key rotation is built in: deploy a new primary key alongside the old one as a
fallback, and active sessions migrate to the new key automatically on their next request.
Install
[]
= "0.1"
= "0.8"
= { = "1", = ["derive"] }
= { = "1", = ["macros", "rt-multi-thread"] }
Quickstart
use ;
use ;
use ;
async
async
async
Key rotation
Rotate a key without invalidating active sessions by listing the new key as primary and
the old as a fallback. Active sessions decrypt under the fallback and silently re-encrypt
under the primary on their next request - within one max_age window, every active
session has migrated.
# use SessionKeys;
#
For multiple generations of fallbacks:
# use SessionKeys;
#
Rotation schedule:
- Deploy with
newas primary andoldas fallback. - Wait for one
max_ageto pass (default 24h) - all active sessions auto-migrate. - Deploy with only
newas primary; dropoldentirely.
The authenticated issued_at inside each cookie is preserved across rotation: migrating
to a new encryption key does not reset the session's age or extend its lifetime.
Configuration reference
| Setter | Default | Purpose |
|---|---|---|
cookie_name(name) |
"session" |
Cookie name. Distinct layers need distinct names. |
path(path) |
"/" |
Path attribute on the emitted cookie. |
domain(host) / no_domain() |
host-scoped | Domain attribute, or omit for host-scoped. |
max_age(d) |
24h | Server-side session lifetime. issued_at + max_age < now means expired. |
secure(bool) |
true |
Secure attribute. Set false only for local HTTP development. |
http_only(bool) |
true |
HttpOnly attribute. Prevents JS access to the cookie. |
same_site(SameSite) |
Lax |
SameSite attribute. Use Strict for CSRF-sensitive flows. |
refresh_after(Option<Duration>) |
None |
Opt-in sliding-refresh threshold (see below). |
Sliding refresh
By default sessions do not extend their lifetime. Enable sliding refresh to re-issue the
cookie with a fresh issued_at after a configurable threshold, keeping active users
logged in while letting idle sessions expire:
use SessionConfig;
use Duration;
let config = default
.max_age
.refresh_after; // refresh after 1 hour of activity
With refresh_after(Some(1h)) and max_age(24h), a request received two hours after
the session was issued produces a Set-Cookie with issued_at = now and the same
payload - the session's effective expiry is pushed back by the fresh issued_at. A
request 25 hours after issue is rejected as expired even with sliding refresh enabled
(past-max-age takes precedence).
Threat model
Protected against:
- Cookie confidentiality. Captured cookies cannot be read without the encryption key.
- Cookie integrity. Any tampering fails AEAD authentication; the server treats the cookie as absent.
- Session-lifetime forgery.
issued_atis authenticated - a client cannot backdate or extend their own session. - Format-version downgrade. The format version byte lives inside the AEAD plaintext; a future
v2server cannot be tricked intov1parsing by byte-level replay.
Not protected against (consumer responsibilities):
- XSS cookie exfiltration.
HttpOnlymitigates, but seshcookie cannot prevent XSS in the application itself. - Replay of a stolen valid cookie within
max_age. Any valid cookie is honored. Consumers requiring revocation should add a generation-counter field to their typed payload and verify it against a server-side value in their auth middleware. - CSRF.
SameSite=Laxdefault mitigates naive CSRF. UseSameSite=Strictor layer CSRF tokens for stronger protection. - Oversize payloads. Browsers cap cookies at ~4 KB. seshcookie does not split payloads. Keep session payloads compact (IDs and claims, not full profiles).
Notes on the session payload type T
The response path suppresses no-op cookie rewrites by SHA-256-comparing the candidate
serialized payload against the one decrypted from the incoming cookie. For this
comparison to work reliably, serde_json must produce byte-identical output for equal
values of T on every serialization.
- Standard types work fine: structs (derived
Serialize),Vec,Option, nested enums, primitives,String,BTreeMap,BTreeSet,[T; N]. - Avoid
HashMap/HashSetinsideT. Their iteration order is non-deterministic across process restarts, which can cause the hash-compare to miss and produce spuriousSet-Cookieemissions on otherwise-read-only handlers. UseBTreeMap/BTreeSetfor key-value or set fields in session payloads. - Float fields may round-trip differently under some
serde_jsonflags; prefer integer or string representations of money, time, or similar precise values.
Comparison with related crates
| Crate | Model | Crypto | Types | Rotation | Sliding refresh |
|---|---|---|---|---|---|
seshcookie |
Stateless (payload in cookie) | ChaCha20-Poly1305 via ring |
Generic over T |
Built-in, auto-migrate | Opt-in (refresh_after) |
biscotti |
Cookie-level crypto primitives (no session layer) | AES-GCM via cookie::PrivateJar |
Untyped | Manual via key list | Not applicable |
tower-sessions |
Session-ID in cookie + pluggable backing store | N/A (ID only) | Untyped map | Implicit via new ID | Backing-store policy |
Pick seshcookie for stateless, strongly-typed sessions with zero server-side storage.
Pick biscotti for cookie-level crypto without a session abstraction. Pick
tower-sessions for stateful sessions with server-side storage.
MSRV
1.95 (edition 2024).
License
MIT. See LICENSE.