March Docs

Bastion.Session

Session — cookie-backed session storage for Bastion.

Sessions are stored as a signed cookie named "_session". The cookie value is: "<hmac_hex>.<base64_payload>" where:

  • base64_payload = base64(serialize(data))
  • hmac_hex = HMAC-SHA256(signing_key, base64_payload)
  • signing_key = Crypto.derive_key(secret_key_base, "session_signing")

The payload is signed but not encrypted. This is safe as long as the session contains only non-secret identifiers (user IDs, CSRF tokens, flash messages). AES-256-GCM encryption will be added once the March runtime exposes the necessary builtins.

Cookie lifetime:

  • Browser session (no Max-Age) by default.
  • Persistent (Max-Age=N) when Session.persist/2 is called before commit.

Session auto-commit: Session.load/2 registers an after-send hook "session_commit" that writes the Set-Cookie header just before the response is flushed. Call Session.load/2 early in the middleware pipeline.

Typical pipeline:

conn
|> Session.load(secret_key_base)
|> CSRF.ensure_token()
|> CSRF.protect()
|> MyApp.Router.route()

Reading and writing session values:

Session.get(conn, "user_id")
Session.put(conn, "user_id", "42")
Session.delete(conn, "user_id")
Session.clear(conn)

Committing explicitly (if not using the after-send hook):

conn |> Session.commit(secret_key_base)

Functions

fnclearclear(conn : Conn) : Conn#

Remove all keys from the session.

    Session.clear(conn)
fncommitcommit(conn : Conn, secret_key_base : String) : Conn#

Write the session back to a Set-Cookie header.

Called automatically by BastionServer via the "session_commit"
after-send hook registered by Session.load/2.  You can also call
it explicitly before redirecting if the hook mechanism isn't available.

    conn |> Session.commit(secret_key_base)
fncommit_from_conncommit_from_conn(conn : Conn) : Conn#

Commit the session using the secret stored by Session.load/2.

Used by BastionServer's after-send hook dispatch — it has only the
conn and looks up the secret from assigns.
fndeletedelete(conn : Conn, key : String) : Conn#

Delete a key from the session.

    Session.delete(conn, "user_id")
fngetget(conn : Conn, key : String) : Option(String)#

Get a session value by key.

    Session.get(conn, "user_id")   -- Some("42") or None
fnloadload(conn : Conn, secret_key_base : String) : Conn#

Load the session from the request cookie and register the commit hook.

Decodes and verifies the "_session" cookie using `secret_key_base`.
If the cookie is absent or has been tampered with, starts a fresh
empty session.

Registers the "session_commit" after-send hook so the session is
automatically written back to a Set-Cookie header after the response.

Call this early in the request pipeline, after CSRF.ensure_token or
as the first middleware step.

    conn |> Session.load(secret_key_base)
fnpersistpersist(conn : Conn, seconds : Int) : Conn#

Configure the session cookie to persist for seconds after creation.

Call before commit/2 or let the commit hook pick it up.

    Session.persist(conn, 60 * 60 * 24 * 60)   -- 60 days
fnputput(conn : Conn, key : String, value : String) : Conn#

Store a value in the session under key.

Marks the session as dirty so it will be committed.

    Session.put(conn, "user_id", user_id)