# `PhoenixKitWeb.Users.MultiSession`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.165/lib/phoenix_kit_web/users/multi_session.ex#L1)

Multi-account session switching.

The Plug session holds an ordered stack of raw session tokens under
`:pk_session_accounts`. `hd/1` of the stack is the ROOT account (the original
login). The currently active token stays in `:user_token`, so all existing auth
resolution (`fetch_phoenix_kit_current_*`, `on_mount`) is untouched.

Read helpers (`gate_allowed?/1`, `list_accounts/1`) take the string-keyed session
map (works from both the plug and the LiveView on_mount). Conn-mutating ops
(`add_account/3`, `add_authenticated_user/2`, `switch_to/2`, `remove_account/2`,
logout helpers) take and return a `Plug.Conn`.

# `add_account`

Validates credentials and appends a real session for that user to the stack,
making it the active account. The new account may be any role; the gate is
enforced by the caller (controller) against the root account.

Returns `{:error, :already_in_stack}` if the user is already present.

# `add_authenticated_user`

Appends an already-authenticated (active) user to the session stack and makes
them the active account. Shares all invariants with `add_account/3`:

- Stack-limit check (`:stack_full`)
- Dedup check — returns `{:error, :already_in_stack}` if the user is already present
- Session-fixation protection via `renew_and_put_active_token/2`

Used by the OAuth add-account callback so the same logic applies whether the
user was authenticated via password or via OAuth.

# `delete_all_stack_tokens`

Deletes every stack token from the DB (used by 'Log out all').

# `gate_allowed?`

True when the root session belongs to ANY authenticated user AND the
`multi_session_enabled` setting is on. Evaluated against the root so the
switcher stays visible even when a secondary account is active.

Anonymous (no root token / no valid user) always returns false.

# `list_accounts`

Resolves each stack token to a render struct:
`%{ref, user, email, role, active?, root?}`. Tokens that no longer resolve to a
user (expired/deleted) are dropped.

# `log_out_active`

Logs out the active account. When a non-root account is active, deletes it and
switches back to root (`{:switched, conn, root_user}`). When the root account is
active, signals a full logout (`{:full, conn}`) for the caller to run.

# `max_accounts`

Maximum number of accounts allowed in one stack.

# `remove_account`

Removes a non-root token from the stack and deletes it from the DB.

# `scope_fields`

Resolves the two transient Scope fields `{multi_session_allowed?, multi_session_accounts}`
for a session in one call.

Crucially, the (DB-heavy) account stack is resolved ONLY when the setting is on.
When `multi_session_enabled` is off — the default — this short-circuits to
`{false, []}` without touching the DB, so the hot auth path (plug + every
LiveView mount) pays nothing for a feature that is disabled.

When it IS on, `allowed?` is derived from the resolved stack (a surviving root
account) rather than a separate `gate_allowed?/1` call, which would re-resolve
the root token in its own query on top of the `list_accounts/1` walk.

# `stack_tokens`

The list of raw session tokens in the stack. Falls back to the single active
token when no explicit stack is stored, and `[]` when there is no active token.

# `switch_to`

Activates a token already present in the stack, identified by `ref`.

---

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