Storage seam for OpenID Connect Back-Channel Logout 1.0 and Front-Channel Logout 1.0.
Both logout mechanisms require the OP to remember which Relying Parties hold
a session for the End-User so it can notify them when that session ends:
Back-Channel Logout by POSTing a signed logout_token to the RP's
backchannel_logout_uri (Back-Channel Logout 1.0 §2.5), Front-Channel
Logout by rendering the RP's frontchannel_logout_uri in an iframe on the
logout page (Front-Channel Logout 1.0 §3). This behaviour is that memory: a
row is recorded each time an ID Token is minted to a logout-capable client
(the OP is the party that mints those tokens, so it is the natural place to
record the binding), and enumerated when the end-session endpoint fires.
This is not the browser login session — attesto never models that; the
host owns login and supplies the sid. This store only persists the OP-side
(sid, client_id) -> logout URIs delivery map, exactly as
Attesto.RefreshStore persists issued refresh families. Killing the actual
login state remains a host concern (the end-session controller's
:terminate_session callback).
Record shape
A stored record (record/1 input) is a map with:
:sid- the session id (the value asserted in the ID Token'ssidclaim). The per-session key the fan-out matches on.:subject- thesubthe session authenticated. The per-subject key, used when logout is requested without asid.:client_id- the Relying Party that received the ID Token.:backchannel_logout_uri- where thelogout_tokenis POSTed, ornilfor an RP that is not back-channel-logout capable.:session_required- the client'sbackchannel_logout_session_required(whether itslogout_tokenMUST carrysid).:frontchannel_logout_uri- the RP URL the logout page renders in an iframe (Front-Channel Logout 1.0 §2), ornilfor an RP that is not front-channel-logout capable.:frontchannel_session_required- the client'sfrontchannel_logout_session_required(whether the rendered URI must carryissandsidquery parameters).:expires_at- absolute expiry, unix seconds (so abandoned sessions are swept; mirror the ID Token / session lifetime).
A record carries at least one of the two logout URIs — a client with neither registers no logout session at all.
record/1 is idempotent on (sid, client_id): re-issuing an ID Token for a
session the RP already has refreshes the row rather than duplicating it.
Summary
Types
The criteria that select which sessions to log out. :sid scopes logout to
one session (across each RP that holds it); :subject (used when no sid
is known) scopes it to every session for the subject. At least one is set.
A stored logout session record (see the module docs).
A fan-out target: one RP to notify. :backchannel_logout_uri drives the
logout_token POST; :frontchannel_logout_uri drives the logout page's
iframe. Either may be nil (never both).
Callbacks
Remove the session rows matched by criteria (same :sid/:subject
precedence as targets/1). Called after the fan-out so a session is
enumerated for logout exactly once.
Record (idempotently on (sid, client_id)) that client_id holds a
logout-capable session sid for subject, reachable at the entry's
back-channel and/or front-channel logout URI. Called when an ID Token is
minted to such a client.
Atomically enumerate and remove the RP targets matched by criteria,
returning them (same :sid/:subject precedence as targets/1). This is the
end-session endpoint's fan-out primitive: doing the enumerate and the delete in
one statement (e.g. DELETE ... RETURNING) means two concurrent logouts of the
same session cannot both observe the rows and double-deliver, and no row that
is inserted mid-logout is silently dropped. Already-expired rows are ignored.
List the RP targets to notify for a logout. When criteria carries a :sid,
scope to that session (every RP that received an ID Token under it); with no
:sid but a :subject, scope to all of that subject's sessions. Returns the
matching target/0s (possibly empty). Implementations SHOULD ignore
already-expired rows.
Types
The criteria that select which sessions to log out. :sid scopes logout to
one session (across each RP that holds it); :subject (used when no sid
is known) scopes it to every session for the subject. At least one is set.
@type entry() :: %{ :sid => String.t(), :subject => String.t(), :client_id => String.t(), optional(:backchannel_logout_uri) => String.t() | nil, optional(:session_required) => boolean(), optional(:frontchannel_logout_uri) => String.t() | nil, optional(:frontchannel_session_required) => boolean(), expires_at: non_neg_integer() }
A stored logout session record (see the module docs).
@type target() :: %{ client_id: String.t(), backchannel_logout_uri: String.t() | nil, sid: String.t() | nil, session_required: boolean(), frontchannel_logout_uri: String.t() | nil, frontchannel_session_required: boolean() }
A fan-out target: one RP to notify. :backchannel_logout_uri drives the
logout_token POST; :frontchannel_logout_uri drives the logout page's
iframe. Either may be nil (never both).
Callbacks
@callback delete(criteria()) :: :ok
Remove the session rows matched by criteria (same :sid/:subject
precedence as targets/1). Called after the fan-out so a session is
enumerated for logout exactly once.
@callback record(entry()) :: :ok
Record (idempotently on (sid, client_id)) that client_id holds a
logout-capable session sid for subject, reachable at the entry's
back-channel and/or front-channel logout URI. Called when an ID Token is
minted to such a client.
Atomically enumerate and remove the RP targets matched by criteria,
returning them (same :sid/:subject precedence as targets/1). This is the
end-session endpoint's fan-out primitive: doing the enumerate and the delete in
one statement (e.g. DELETE ... RETURNING) means two concurrent logouts of the
same session cannot both observe the rows and double-deliver, and no row that
is inserted mid-logout is silently dropped. Already-expired rows are ignored.
List the RP targets to notify for a logout. When criteria carries a :sid,
scope to that session (every RP that received an ID Token under it); with no
:sid but a :subject, scope to all of that subject's sessions. Returns the
matching target/0s (possibly empty). Implementations SHOULD ignore
already-expired rows.