arizona_session (arizona v0.4.0)
View SourceEncrypted, durable session cookie codec.
A session is a small map of state carried across requests in a dedicated cookie.
The cookie is encrypted (AES-256-GCM, via arizona_crypto) with the arizona
secret_key application env and stamped with an absolute expiry, so a client can
neither read, forge, nor outlive it. Unlike arizona_flash, a session is
durable: a plain read does not consume or re-emit it. The cookie is rewritten
only when the app writes the session (arizona_req:put_session/3,
delete_session/2) and cleared only on arizona_req:clear_session/1 (logout).
This module owns the wire format: encode/decode the payload and build the
Set-Cookie tuples. arizona_req integrates it into the request/response stash
(put_session/3, session/1, read_session/1, ...), and the
arizona_middleware:fetch_session/2 step reads it into the session binding.
The cookie is encrypted, so its contents are hidden from the client -- but it still lives on the client and a cookie store cannot be revoked before its expiry. Keep sessions small (an id plus light state, well under the ~4KB cookie limit); a server-side store is the place for large, long-lived, or instantly-revocable state.
Configuration (arizona app env)
secret_key-- required (seearizona_crypto); the encryption key.session_max_age-- cookie lifetime in seconds (default 7 days).session_secure--Securecookie flag (defaultfalse). Set this totruein production so the session cookie is only sent over HTTPS. It defaults tofalsebecause aSecurecookie is silently dropped over plain HTTP -- breaking local dev with no error -- and a deployment behind a TLS-terminating proxy can't be auto-detected. Treat enabling it as a deploy-checklist item (mirroringflash_secure). Enabling it also switches the cookie to the__Host-az_sessionname (seecookie_name/0), whose browser-enforced__Host-prefix closes sibling-subdomain cookie tossing.session_max_bytes-- max serializedSet-Cookieline size, name and attributes included (default 4096, what browsers count against their ~4KB cap);encode/1errors with{session_too_large, Size, Limit}past it rather than letting the browser drop it.session_store-- opt into a server-side store (anarizona_session_storemodule, e.g.arizona_session_store_ets); the cookie then carries only a signed opaque id (encode_id/1,decode_id/1,set_cookie_id/1) and the map lives in the store, enabling revocation and large state. Unset (the default) keeps the encrypted cookie store. The mode switch is inarizona_req. The id is signed with the samesession_max_ageexpiry the cookie and the store entry carry, so expiry never rests on the store alone.
Summary
Functions
The Set-Cookie tuple that clears the session (logout).
The session cookie name: az_session, or __Host-az_session when
session_secure is enabled.
Decodes and authenticates a cookie value into a session map, returning #{} when
the value is tampered, malformed, or its baked-in expiry has passed.
Recovers a session id from a cookie value, returning error if tampered,
malformed, expired, or signed under another purpose.
Encodes a session map into an encrypted cookie value with a baked-in absolute
expiry (max_age/0 seconds from now). Errors if secret_key is unset, or with
{session_too_large, Size, Limit} if the serialized Set-Cookie line -- the
cookie name, the encoded value, and the attributes, which is what browsers count
against their ~4KB cap -- exceeds session_max_bytes (default 4096): a loud
failure at write time instead of a cookie the browser silently drops.
Signs a session id into the cookie value (the id is not secret, just unforgeable),
with a max_age/0 expiry baked into the signature. The store entry carries the
same TTL, so the crypto expiry costs a live session nothing -- it is the backstop
for the case where the store's own expiry does not fire (a custom backend that
ignores its TtlSecs, a restored backup, a clock jump), which would otherwise let
a captured cookie resolve to a live session forever.
Formats arizona_session runtime errors into a human-readable message. Picked up
by erl_error:format_exception/3 via the error_info annotation at the raise site.
Normalizes a session key (an atom is converted to a binary).
The configured session lifetime in seconds (session_max_age, default 7 days). The
cookie max-age, the encrypted-payload TTL, and a server-side store entry's TTL are all
this value, so browser and server expiry agree.
A fresh opaque session id (128 random bits, lowercase hex).
The session Set-Cookie a response should carry, given the desired next-state
session and whether it was written this request (Dirty). Durable semantics: a
clean request (untouched) emits none so the client keeps its cookie; a dirty
non-empty session is freshly encrypted and set; a dirty empty session (logout)
clears the cookie.
The Set-Cookie tuple that carries Session to the next request.
The Set-Cookie tuple carrying the signed session Id (store mode).
Functions
-spec clear_cookie() -> {binary(), binary(), arizona_req:resp_cookie_opts()}.
The Set-Cookie tuple that clears the session (logout).
-spec cookie_name() -> binary().
The session cookie name: az_session, or __Host-az_session when
session_secure is enabled.
The __Host- prefix makes the browser refuse to store the cookie unless it is
Secure, Path=/, and Domain-less -- all of which the session cookie already
is when session_secure is on. That closes sibling-subdomain cookie tossing: a
compromised evil.example.com can plant an az_session cookie for
.example.com scoped to a more specific Path, which browsers order ahead of
the real cookie -- and a planted (validly encrypted, attacker-minted) session
would survive login rotation. A __Host- cookie cannot be set with a Domain
at all, so no sibling host can shadow it. Read and write sides both resolve the
name here, so they always agree.
-spec decode(Value) -> arizona_req:session() when Value :: binary().
Decodes and authenticates a cookie value into a session map, returning #{} when
the value is tampered, malformed, or its baked-in expiry has passed.
Recovers a session id from a cookie value, returning error if tampered,
malformed, expired, or signed under another purpose.
-spec encode(Session) -> binary() when Session :: arizona_req:session().
Encodes a session map into an encrypted cookie value with a baked-in absolute
expiry (max_age/0 seconds from now). Errors if secret_key is unset, or with
{session_too_large, Size, Limit} if the serialized Set-Cookie line -- the
cookie name, the encoded value, and the attributes, which is what browsers count
against their ~4KB cap -- exceeds session_max_bytes (default 4096): a loud
failure at write time instead of a cookie the browser silently drops.
Signs a session id into the cookie value (the id is not secret, just unforgeable),
with a max_age/0 expiry baked into the signature. The store entry carries the
same TTL, so the crypto expiry costs a live session nothing -- it is the backstop
for the case where the store's own expiry does not fire (a custom backend that
ignores its TtlSecs, a restored backup, a clock jump), which would otherwise let
a captured cookie resolve to a live session forever.
-spec format_error(Reason, Stacktrace) -> ErrorInfo when Reason :: term(), Stacktrace :: [tuple()], ErrorInfo :: #{general := iolist()}.
Formats arizona_session runtime errors into a human-readable message. Picked up
by erl_error:format_exception/3 via the error_info annotation at the raise site.
Normalizes a session key (an atom is converted to a binary).
-spec max_age() -> non_neg_integer().
The configured session lifetime in seconds (session_max_age, default 7 days). The
cookie max-age, the encrypted-payload TTL, and a server-side store entry's TTL are all
this value, so browser and server expiry agree.
-spec new_id() -> binary().
A fresh opaque session id (128 random bits, lowercase hex).
-spec resp_cookie(SessionOut, Dirty) -> {binary(), binary(), arizona_req:resp_cookie_opts()} | none when SessionOut :: arizona_req:session(), Dirty :: boolean().
The session Set-Cookie a response should carry, given the desired next-state
session and whether it was written this request (Dirty). Durable semantics: a
clean request (untouched) emits none so the client keeps its cookie; a dirty
non-empty session is freshly encrypted and set; a dirty empty session (logout)
clears the cookie.
-spec set_cookie(Session) -> {binary(), binary(), arizona_req:resp_cookie_opts()} when Session :: arizona_req:session().
The Set-Cookie tuple that carries Session to the next request.
-spec set_cookie_id(Id) -> {binary(), binary(), arizona_req:resp_cookie_opts()} when Id :: binary().
The Set-Cookie tuple carrying the signed session Id (store mode).