Skip to content
Spice Framework on GitHub

Encrypted cookie sessions

frameworkMaturity: previewSource: spice@c0641b3Exact reviewed source

session provides typed, stateless HTTP sessions with no global registry, background worker, process-environment read, or server-side storage:

type Claims struct {
Subject string `json:"subject"`
Roles []string `json:"roles"`
}
sessions, err := session.New[Claims](session.Options{
Name: "__Host-orders",
Keys: []session.Key{
{
ID: "2026-07",
Secret: configuration.SessionKey,
},
{
ID: "2026-06",
Secret: configuration.PreviousSessionKey,
},
},
})
if err != nil {
return err
}

Keys are exactly 32 bytes for AES-256-GCM. Load them from a secret source and never commit them. The first key seals new cookies; at most seven older keys may remain for bounded decryption during rotation. A record loaded through an older key reports NeedsRefresh, so the application can save it again with the primary key before retiring the old key.

record, err := sessions.Load(request, clock())
switch {
case errors.Is(err, session.ErrNotFound):
// Continue anonymously.
case errors.Is(err, session.ErrExpired):
// Ask the client to authenticate again.
case err != nil:
// Treat malformed or unauthenticated state as an authentication failure.
default:
principal := record.Value
}
if err := sessions.Save(writer, claims, clock()); err != nil {
return err
}

Cookies are host-only, Secure, HttpOnly, Path=/, and SameSite=Lax by default. __Host- and __Secure- prefix rules are enforced. SameSite=None requires Secure. AllowInsecure exists only for isolated local HTTP tests; it is explicit and cannot weaken prefixed or cross-site cookies.

Session JSON is encrypted and authenticated with a fresh cryptographic nonce. The token version, cookie name, and key ID are authenticated associated data. Payloads are limited to 2 KiB, complete cookie headers to 4 KiB, lifetimes to 30 days, clock skew to five minutes, and key sets to eight entries. Decoding rejects duplicate cookies, unknown JSON fields, trailing values, null values, unknown keys, tampering, future issuance beyond skew, and expiry. Errors never contain plaintext or token contents.

Clear emits deletion with the same path, domain, security, and SameSite attributes. Managers are immutable and safe for concurrent use.

This is a stateless bearer session: confidentiality does not prevent replay, and removing a key invalidates every cookie under that key. Applications that need per-session revocation, large state, or clustered mutable sessions should store only an opaque identifier in the encrypted cookie and provide an explicit server-side store. SameSite is a CSRF defense-in-depth control, not a replacement for origin checks or CSRF tokens on sensitive browser actions.