# `Attesto.EndSession`
[🔗](https://github.com/XukuLLC/attesto/blob/v0.13.0/lib/attesto/end_session.ex#L1)

Validate an OpenID Connect RP-Initiated Logout request
(OpenID Connect RP-Initiated Logout 1.0 §2-3).

The end-session endpoint is where a Relying Party sends the End-User's
browser to log out at the OP. Like the rest of attesto core this module is
pure — it parses and validates the request parameters and never touches a
store, a session, or a `Plug.Conn`. The host's controller owns loading the
client, terminating the browser session, and (for Back-Channel Logout)
fanning logout tokens out to the RPs.

Two steps, because the second needs data the controller must fetch in
between:

  1. `parse/2` validates the request parameters, verifies the
     `id_token_hint` (tolerating expiry — see
     `Attesto.IDToken.verify_logout_hint/2`), and resolves the Relying
     Party `client_id` and the session identifiers (`sub` / `sid`) the
     back-channel fan-out will key on. It returns a `t:t/0` the controller
     uses to load the client's registered `post_logout_redirect_uris`.
  2. `confirm_redirect/2` checks the requested `post_logout_redirect_uri`
     against that registered list (exact string match, RP-Initiated Logout
     §2/§3 — no normalization, no prefix matching) and returns the final
     redirect target with `state` appended, or `:no_redirect` when the RP
     asked for none.

Resolving the client before validating the redirect URI is what makes an
open-redirect impossible: a `post_logout_redirect_uri` is only ever honored
when it exactly matches a value the client registered.

# `parse_error`

```elixir
@type parse_error() :: :invalid_id_token_hint | :client_id_mismatch
```

# `t`

```elixir
@type t() :: %Attesto.EndSession{
  client_id: String.t() | nil,
  logout_hint: String.t() | nil,
  post_logout_redirect_uri: String.t() | nil,
  sid: String.t() | nil,
  state: String.t() | nil,
  subject: String.t() | nil,
  ui_locales: String.t() | nil
}
```

A parsed, hint-verified end-session request.

  * `:client_id` - the Relying Party, resolved from the `id_token_hint`'s
    `aud` and/or the `client_id` parameter (nil when the request carried
    neither, in which case no `post_logout_redirect_uri` can be honored).
  * `:subject` / `:sid` - the session identifiers from the `id_token_hint`
    (nil when no hint was supplied); the keys a Back-Channel Logout fan-out
    uses to find the RP sessions to notify.
  * `:post_logout_redirect_uri` / `:state` - the requested return target and
    opaque state, echoed back only after the URI is confirmed registered.
  * `:logout_hint` / `:ui_locales` - passthrough hints (RP-Initiated Logout
    §2) for the host's logout-confirmation UI.

# `confirm_redirect`

```elixir
@spec confirm_redirect(t(), [String.t()]) ::
  {:ok, String.t() | :no_redirect} | {:error, :invalid_post_logout_redirect_uri}
```

Confirm the request's `post_logout_redirect_uri` against the Relying Party's
registered list and compute the final redirect target.

`registered_uris` is the client's registered `post_logout_redirect_uris`
(the host loads them from its `Attesto.ClientStore` between `parse/2` and
this call). Matching is exact-string (RP-Initiated Logout §2/§3).

Returns:

  * `{:ok, :no_redirect}` - the request asked for no return URI; the host
    should render its own logged-out page.
  * `{:ok, url}` - the validated `post_logout_redirect_uri` with the
    request's `state` appended as a query parameter when present.
  * `{:error, :invalid_post_logout_redirect_uri}` - the requested URI is not
    registered (or the RP could not be identified), so it MUST NOT be used.

# `parse`

```elixir
@spec parse(Attesto.Config.t(), map()) :: {:ok, t()} | {:error, parse_error()}
```

Parse and validate the end-session request `params` (string- or atom-keyed).

Recognized parameters (OpenID Connect RP-Initiated Logout 1.0 §2):
`id_token_hint`, `client_id`, `post_logout_redirect_uri`, `state`,
`logout_hint`, `ui_locales`.

When `id_token_hint` is present it is verified via
`Attesto.IDToken.verify_logout_hint/2` (signature + issuer, expiry
tolerated); a hint that fails verification is `:invalid_id_token_hint`. The
Relying Party `client_id` is taken from the hint's `aud`; if the `client_id`
parameter is **also** present it MUST equal it (`:client_id_mismatch`,
RP-Initiated Logout §2). With no hint, the `client_id` parameter alone
identifies the RP.

Returns `{:ok, %Attesto.EndSession{}}` or `{:error, reason}`.

---

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