AttestoPhoenix.Controller.EndSessionController (AttestoPhoenix v0.19.0)

Copy Markdown View Source

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. context is %{subject, sid, client_id} carrying the id_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 a logout_token to the RPs of this host-confirmed session. The fan-out scope comes from here, NOT from the request's id_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 no post_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

end_session(conn, params)

@spec end_session(Plug.Conn.t(), map()) :: Plug.Conn.t()