Session Management
Stateful authentication and data persistence using secure, HttpOnly cookies with pluggable storage backends.
1 Storage Backends
In-Memory
Default. Fast but lost on server restart.
Disk
File-based JSON storage for persistence.
SolidB
HTTP database backend for distributed deployments.
SoliKV
Redis-compatible key-value store with TTL.
Cookie (encrypted, client-side)
The whole session travels in the cookie, sealed with AES-256-GCM. Survives restarts and works across hosts with zero infrastructure. Requires SOLI_SESSION_SECRET.
Configuration Methods
Sessions can be configured via code, environment variables, or config files:
# Change storage backend at runtime
session_configure({"driver": "solidb", "solidb_host": "db.example.com"})
# Encrypted client-side sessions (no session database)
session_configure({"driver": "cookie", "secret": getenv("SOLI_SESSION_SECRET")})
export SOLI_SESSION_DRIVER=solidb
export SOLI_SOLIDB_HOST=db.example.com
# Or: encrypted cookie sessions
export SOLI_SESSION_DRIVER=cookie
export SOLI_SESSION_SECRET=$(openssl rand -hex 32) # 32+ chars, keep stable
Encrypted Cookie Sessions
The cookie driver stores the session on the client, Rails-style:
the payload is sealed with AES-256-GCM (key HKDF-derived from
SOLI_SESSION_SECRET), so clients can neither read nor forge it.
A tampered or expired blob is silently replaced by a fresh empty session, and the cookie is only
re-emitted when the session actually changed.
- ~4KB ceiling — browsers cap cookies at 4096 bytes. An oversized session refuses to seal (loud log line, previous cookie kept). Store identifiers, not records.
- No server-side revocation —
session_destroy()overwrites the client's copy, but a stolen cookie stays valid until its TTL passes. Rotating the secret invalidates every outstanding session at once. - TTL counts from the last write — expiry uses a timestamp sealed inside the payload, refreshed on each write.
Need instant logout-everywhere or sessions bigger than a cookie? Use a server-side driver
(solidb, solikv,
disk) instead.
1 Basic Operations
Use the built-in session helper functions to read, write, and manage user data.
# 1. Write data to the session
session_set("user_id", 42)
session_set("username", "alice_w")
session_set("role", "admin")
# 2. Read data (returns null if not found)
current_user = session_get("username")
# 3. Check if key exists
if session_has("user_id")
print("User is authenticated!")
end
# 4. Remove specific data
session_delete("flash_message")
# 5. Clear everything (logout)
session_destroy()
2 Security Best Practices
Session Fixation Protection
When a user logs in or elevates privileges, always regenerate the session ID. This prevents session fixation attacks where an attacker tricks a user into using a known session ID.
def login(req: Any)
credentials = req["body"]
if verify_user(credentials)
# CRITICAL: Regenerate session ID before setting sensitive data
session_regenerate
session_set("authenticated", true)
session_set("user_id", credentials["id"])
return redirect("/dashboard")
end
render("login", { "error": "Invalid credentials" })
end
Cookie Hardening
Session cookies always carry HttpOnly and Path=/. The SameSite attribute and __Host- prefix are configurable via environment variables.
| Variable | Purpose | Default |
|---|---|---|
SOLI_SESSION_SAMESITE | Lax, Strict, or None. Strict blocks the cookie on any cross-site navigation; None automatically pairs with Secure — Soli forces the flag on regardless of the detected request scheme so browsers don't silently drop the cookie. | Lax |
SOLI_SESSION_HOST_PREFIX | Set to 1 to emit the cookie as __Host-session_id. Browsers only accept __Host- cookies that are Secure and scoped to Path=/ with no Domain — this prevents subdomain takeover from setting an attacker-controlled session cookie. Applied only when Secure is also active. | unset |
export SOLI_SESSION_SAMESITE=Strict
export SOLI_SESSION_HOST_PREFIX=1