Architecture Decision Record

0044. Refresh tokens, session linkage, and logout

Context

Three decisions were made in pieces and never joined. ADR-0014 adopted refresh tokens with rotation and reuse detection but not where family state lives, how a legitimate client's network retry is distinguished from theft, or how long a family may live. ADR-0016 modeled the browser SSO session and required back-channel logout — which needs a sid claim and a way to enumerate "all tokens this session minted", neither of which any record defined. And the corpus was silent on two standard endpoints every relying party asks about: token revocation (RFC 7009) and token introspection (RFC 7662). The design review flagged all three as gaps that harden into the token format and the client model, so they are fixed together here, before the format spec is written.

Decision

  1. Tokens are session-linked through the sid claim. Every access, ID, and refresh token carries the sid of the session (or, for browserless clients, the session-equivalent anchor this ADR defines) that minted it — a v0 claim seat (ADR-0003). Session termination revokes by sid: one status-list entry (ADR-0005) kills every token the session produced, with no per-token enumeration, and the back-channel logout token (ADR-0016) names the same sid.

  2. A refresh-token family is the browserless session. Native, mobile, and CLI clients have no cookie; their long-lived anchor is the refresh-token family — all rotations of one original grant. The family record (id, sid, client, principal, scopes, creation and last-use times, current-generation token hash) lives in the same region-local, eventually-consistent store as the SSO session (ADR-0016's SessionStore): rotation is a region-local write on the issuance side, never a strong core write on a hot path (rule 2), and "session" management UIs (proposal 0010) list cookie sessions and token families uniformly.

  3. Rotation with a bounded retry-grace window. Each use rotates the token and invalidates its predecessor (ADR-0014). A predecessor presented within a short grace window (seconds-order, configured in the ADR-0039 registry) and from the same client is answered with the same successor already issued — the legitimate-client network-retry case, which a naive family-kill turns into a false theft alarm and a forced re-login. A predecessor presented past the grace window, or a token two or more generations old, is the theft signal: the family is revoked through the status list (its sid entry) and the event feeds the risk pipeline (ADR-0031).

  4. Families have per-client lifetimes. An idle timeout (no refresh within T_idle ends the family) and an absolute lifetime (the family dies at T_max regardless of use), both per-client registered fields (ADR-0038) validated in the budget registry (ADR-0039) — CIAM realms may keep users signed in for months; a workforce realm may cap at a workday. Web-client families are further capped by their SSO session's lifetime (decision 2's linkage).

  5. RFC 7009 token revocation ships in v1 — for refresh tokens. A client calling /revoke on a refresh token ends its family (and its sid coverage does the rest). Revoking an access token individually is accepted but is normally a no-op beyond its short TTL — the response is honest about that.

  6. RFC 7662 introspection is explicitly declined. An introspection endpoint is a per-request core-adjacent read — precisely the architecture rule 1 exists to refuse; Vord's answer to "is this token good now?" is local JOSE verification plus the status list (ADR-0010, ADR-0032). Relying parties that cannot verify locally use the SDK's verifier, not a network oracle. Declined with the same finality as ROPC and the implicit flow (ADR-0014), not deferred.

  7. The v1 logout endpoint set, joining ADR-0014 and ADR-0016: RP-initiated logout (end_session_endpoint — the user-visible entry point) and OIDC Back-Channel Logout (the RP notification path, using the sid). Front-Channel Logout is declined: it depends on third-party cookies and hidden iframes that current browsers increasingly break, so it offers a false sense of coverage back-channel + short TTL provide honestly.

Consequences

  • Logout is now mechanically complete end to end: cookie or refresh-family anchor → sid → one status-list entry → edge rejection within the freshness budget + RP notification — every step named, every artifact enumerable.
  • The retry-grace window turns reuse detection from a false-positive generator into a usable theft alarm; the window is a stated budget, not folklore.
  • Refresh-family state joins session state in the region-local store — no new consistency domain, no strong hot-path write, and one uniform "active sessions" view for users and admins.
  • Declining introspection keeps the no-core-read thesis intact at the one endpoint where relying parties would quietly reintroduce it; the SDK carries the local verifier instead.
  • Refines ADR-0014 (flow set), ADR-0016 (session model), ADR-0038 (per-client lifetime fields); consumes the sid seat of ADR-0003; registers its windows in ADR-0039.