End-session endpoint (OpenID Connect RP-Initiated Logout 1.0 §2 + Back-Channel Logout 1.0).
Where a Relying Party sends the End-User's browser to log out. This
controller owns the protocol — it verifies the id_token_hint, validates the
post_logout_redirect_uri against the RP's registered set, fans a
logout_token out to every other RP holding the session, and either
redirects to the validated return URI or hands off to the host's logged-out
page — while the host owns the browser session and the HTML through two
callbacks:
:terminate_session(REQUIRED when logout is enabled) —(conn, context -> {:ok, conn} | {:ok, conn, session} | {:halt, conn}). Clears the host's browser login session.contextis%{subject, sid, client_id}carrying theid_token_hint's values (any may be nil). The host is the authority on the session:{:ok, conn}— the current session was cleared; run front-channel logout only (no back-channel fan-out).{:ok, conn, %{sid: ..., subject: ...}}— the current session was cleared; fan out alogout_tokento the RPs of this host-confirmed session. The fan-out scope comes from here, NOT from the request'sid_token_hint, so a replayed or stolen ID Token cannot force-log-out an arbitrary session.{:halt, conn}— the host has taken over the response entirely (e.g. to render a logout-confirmation step); nothing further runs.
:render_logged_out(optional) —(conn, context -> conn). Renders the "you are now logged out" page when the request asked for nopost_logout_redirect_uri. A minimal 200 is sent when unset.
Host responsibility: session binding + CSRF (REQUIRED)
RP-Initiated Logout is a state-changing action reachable by GET (the RP
redirects the browser here). The id_token_hint is not an authenticator —
it is signed by the OP and any party that holds a copy (a malicious RP, a
leaked token) can present it. The library therefore makes the host the session
authority: :terminate_session MUST verify that the request corresponds to
the current OP browser session (its cookie) before clearing it and before
returning an {:ok, conn, session} that drives back-channel fan-out. A host
that wants an explicit confirmation step returns {:halt, conn} and renders
its own page. The controller additionally requires HTTPS. Without this
binding, /end_session is a logout-CSRF / forced-logout primitive.
Redirect safety
A post_logout_redirect_uri is honored only when it exactly matches one
the client registered (RP-Initiated Logout §2/§3); the RP is identified from
the verified id_token_hint's aud (or the client_id parameter). A request
that names an unregistered URI — or supplies one with no way to identify the
client — is refused before any session is touched, so the endpoint cannot be
turned into an open redirector.
Back-Channel Logout (Back-Channel Logout 1.0 §2.5)
When :terminate_session returns a confirmed session, the OP atomically takes
(enumerates-and-deletes) that session's recorded (sid|subject) rows and
POSTs a signed logout_token to each RP's backchannel_logout_uri (recorded
at mint time, see Attesto.LogoutSessionStore). The take is atomic so
concurrent logouts cannot double-deliver. Delivery is best-effort: a slow or
failing RP is logged, never allowed to stall the user's logout. Requires a
:logout_session_store; without one, only RP-Initiated (front-channel) logout
runs.
Summary
Functions
@spec end_session(Plug.Conn.t(), map()) :: Plug.Conn.t()