# `AttestoPhoenix.BrowserState`
[🔗](https://github.com/XukuLLC/attesto_phoenix/blob/v2.1.0/lib/attesto_phoenix/browser_state.ex#L1)

The OP browser-state cookie (OpenID Connect Session Management 1.0 §3.2).

Session Management binds the RP-visible `session_state` value to an opaque
browser-scoped value at the OP origin — the *OP User Agent state* — so the
`check_session_iframe` can recompute the hash purely in the browser. The
spec's constraints shape the cookie exactly:

  * the iframe's JavaScript must read it, so it is **not** `HttpOnly` (§3.2);
  * the iframe is embedded cross-site (in the RP's page), so the cookie is
    `SameSite=None; Secure` or the browser would not send it to — or expose
    it inside — the third-party iframe context;
  * it is a `__Host-`-prefixed cookie: the browser then rejects it unless it
    is `Secure`, `Path=/`, and carries **no** `Domain` attribute, so a
    sibling/parent-domain origin cannot inject or shadow it and logout (which
    expires the host cookie) fully clears it;
  * it changes when the End-User's login state at the OP changes: this
    module mints it when an authorization response is issued (login) and
    expires it at the end-session endpoint (logout), and rotates it whenever
    the login binding changes, so a recomputed `session_state` flips to
    `changed`.

The value is **OP-owned and login-bound**: it is not a bare random string but
the integrity-protected `random . login_tag . mac` minted by
`Attesto.SessionState.mint_browser_state/2`. `ensure/3` only reuses an
incoming cookie whose MAC verifies under the OP secret *and* whose login tag
still matches the current authorization's login binding; otherwise it mints a
fresh value. That defends two things at once: an injected/forged cookie
cannot forge `unchanged` (its MAC will not verify), and a re-auth / account
switch rotates the state (its login tag no longer matches) so earlier RP
`session_state` values become `changed` (Session Management 1.0 §3.2).

The cookie name and lifetime come from `AttestoPhoenix.Config`'s
`session_management: [browser_state_cookie: ..., browser_state_cookie_max_age: ...]`;
the OP secret is `session_management: [browser_state_secret: ...]` (required
when session management is enabled).

# `current`

```elixir
@spec current(Plug.Conn.t(), AttestoPhoenix.Config.t()) :: String.t() | nil
```

The request's browser-state cookie value, or `nil`.

# `ensure`

```elixir
@spec ensure(Plug.Conn.t(), AttestoPhoenix.Config.t(), binary()) ::
  {Plug.Conn.t(), String.t()}
```

Ensure the OP browser-state cookie exists and is current for `login_binding`.

Reuses the incoming cookie only when it is a value this OP minted (MAC
verifies under the configured secret) that is still bound to the current
login state; otherwise mints a fresh value and sets the response cookie.
`login_binding` is a stable string derived from the current authorization's
resolved login identity (subject / auth_time / sid). Returns `{conn, value}`.

# `expire`

```elixir
@spec expire(Plug.Conn.t(), AttestoPhoenix.Config.t()) :: Plug.Conn.t()
```

Expire the browser-state cookie (logout — §3.2: the OP browser state changes
when the End-User's login state does). A subsequent `check_session_iframe`
recomputation hashes over the empty state and yields `changed`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
