ESC
Type to search...
S
Soli Docs

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

controllers/session_controller.sl
# 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.

controllers/auth_controller.sl
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.

VariablePurposeDefault
SOLI_SESSION_SAMESITELax, 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_PREFIXSet 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