Kemal::Session
Inherits Reference < Object
A cookie-store session for Kemal: the entire session lives inside a single AES-256-GCM encrypted, tamper-proof cookie. Nothing is stored server-side — there is no storage engine, no session id registry, and no GC.
Unlike kemal-session (a server-side store keyed by a signed id), this keeps
all session data in the cookie itself; the public value API is kept
compatible with kemal-session. As a bonus, the cookie's wire format is
byte-compatible with Rails' ActionDispatch::Session::CookieStore, so a
Kemal and a Rails 8 app can share sessions.
The session hash is flat, so a key lives in a single namespace rather than
one-per-type: session.int("n", 1) and session.string("n", "x") refer to
the same key "n".
Constants
The largest cookie browsers are required to accept, and the limit Rails
enforces (ActionDispatch::Cookies::MAX_COOKIE_SIZE).
Constructors
Builds a session from a request context, decrypting the incoming cookie (if present and valid). An invalid/tampered cookie yields an empty session.
Class methods
Yields the config for block-style setup:
Kemal::Session.config { |c| c.secret = "..." }.
Instance methods
Writes the cookie for this request, if the session was mutated, and marks
the session as sent — after which further mutations raise
ResponseAlreadySent.
Session::Handler calls this immediately before the first byte of the
response body (the last moment the Set-Cookie header can still change),
and again for responses that end without a body. Calling it twice is
harmless: the second call has nothing to write.
Removes a key from the session (regardless of value type) and persists.
Clears the session and expires the cookie. The cookie stays expired for
the rest of the request: anything that repopulates the store afterwards
(a layout rendering csrf_meta_tags after a logout, say) would otherwise
save a fresh live cookie and undo the call.
Whether #destroy was called on this session. A destroyed session still
works in memory for the rest of the request, but never writes a live
cookie again — only #reset starts a new one.
Whether the store has been mutated since the cookie was last written.
Mutations only mark the session dirty; Session::Handler encrypts and
writes the cookie once per request (see #commit).
float stays strict: a whole number is an integer, not a float. Widening
the guard to Int | Float would break the guard symmetry the flat store
relies on, since int? would still reject 5.0.
float stays strict: a whole number is an integer, not a float. Widening
the guard to Int | Float would break the guard symmetry the flat store
relies on, since int? would still reject 5.0.
float stays strict: a whole number is an integer, not a float. Widening
the guard to Int | Float would break the guard symmetry the flat store
relies on, since int? would still reject 5.0.
The session_id. Generated (and persisted to the cookie) on first
access if not already present.
int is Int64 — the width JSON integers already have in the store — so
no stored integer is ever too wide to read back.
int is Int64 — the width JSON integers already have in the store — so
no stored integer is ever too wide to read back.
int is Int64 — the width JSON integers already have in the store — so
no stored integer is ever too wide to read back.
Stores any JSON-serializable value (a JSON::Serializable object, an
Array/Hash of them, or a primitive) under key and persists.
session.object("cart", cart)
Reads back a complex value, reconstructing it as type. as is a
keyword argument so this never collides with the writer overload.
Raises KeyError if the key is absent.
session.object("cart", as: Cart)
session.object("items", as: Array(Item))
Like #object, but returns nil when the key is absent or the stored
value cannot be reconstructed as type.
Clears all data and starts a fresh session (new session_id). This
deliberately starts a new session, so it lifts a previous #destroy.
Serializes and re-encrypts the session into the response cookie. Prefer
letting Session::Handler do this once per request via #commit; calling
it directly writes the cookie immediately, which only works while the
response body has not started. A no-op for detached sessions.
A session that has become empty — or that was destroyed — expires the cookie rather than leaving the last value the client was given in place; otherwise deleting the final key (a hand-rolled logout) would silently keep the old session alive.
Raises CookieOverflow if the session no longer fits in a cookie. The
dirty flag is cleared first, so an overflowing session raises once, from
whichever call tried to write it, rather than again from every later
commit.
Whether the cookie for this request has already been handed to the
response. Past that point the Set-Cookie header is fixed, so further
mutations raise rather than being lost.
The underlying flat session hash. Values Rails would set are visible here and vice-versa.
Macros
Generates, for a given name/Crystal-type: name(k) -> value (raises KeyError if absent/wrong type) name?(k) -> value? (nil if absent/wrong type) name(k, v) -> v (set + persist)
Deletion is type-agnostic (the store is flat): see #delete.
Nested types
- Kemal::Session::CSRF
- Kemal::Session::Config
- Kemal::Session::CookieOverflow
- Kemal::Session::Crypto
- Kemal::Session::Error
- Kemal::Session::ExpiredMessage
- Kemal::Session::Flash
- Kemal::Session::Handler
- Kemal::Session::HandlerRequired
- Kemal::Session::InvalidAuthenticityToken
- Kemal::Session::InvalidMessage
- Kemal::Session::MessageEncryptor
- Kemal::Session::ResponseAlreadySent
- Kemal::Session::SecretRequiredException