arizona_session (arizona v0.4.0)

View Source

Encrypted, 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 (see arizona_crypto); the encryption key.
  • session_max_age -- cookie lifetime in seconds (default 7 days).
  • session_secure -- Secure cookie flag (default false). Set this to true in production so the session cookie is only sent over HTTPS. It defaults to false because a Secure cookie 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 (mirroring flash_secure). Enabling it also switches the cookie to the __Host-az_session name (see cookie_name/0), whose browser-enforced __Host- prefix closes sibling-subdomain cookie tossing.
  • session_max_bytes -- max serialized Set-Cookie line size, name and attributes included (default 4096, what browsers count against their ~4KB cap); encode/1 errors with {session_too_large, Size, Limit} past it rather than letting the browser drop it.
  • session_store -- opt into a server-side store (an arizona_session_store module, 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 in arizona_req. The id is signed with the same session_max_age expiry 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

clear_cookie()

-spec clear_cookie() -> {binary(), binary(), arizona_req:resp_cookie_opts()}.

The Set-Cookie tuple that clears the session (logout).

decode(Value)

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

decode_id(Value)

-spec decode_id(Value) -> {ok, binary()} | error when Value :: binary().

Recovers a session id from a cookie value, returning error if tampered, malformed, expired, or signed under another purpose.

encode(Session)

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

encode_id(Id)

-spec encode_id(Id) -> binary() when Id :: binary().

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.

format_error(Reason, Stacktrace)

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

key/1

-spec key(atom() | binary()) -> binary().

Normalizes a session key (an atom is converted to a binary).

max_age()

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

new_id()

-spec new_id() -> binary().

A fresh opaque session id (128 random bits, lowercase hex).

resp_cookie(SessionOut, Dirty)

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

set_cookie(Session)

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