Skip to content

feat(auth): revoke Explorer sessions from Auth's signed back-channel logout - #19

Merged
jzhao234 merged 1 commit into
mainfrom
feat/backchannel-revocation
Sep 14, 2026
Merged

jzhao234 merged 1 commit into
mainfrom
feat/backchannel-revocation

Conversation

@jzhao234

Copy link
Copy Markdown
Contributor

TLDR

Explorer accepts signed OpenID Connect back-channel logout tokens from Auth and immediately ends every Explorer session for the affected account. Closes the gap the README called out after #17.

Fix

  • POST /auth/backchannel-logout, public by design, Content-Length required and capped at 20 KB.
  • Token verification: logout+jwt, EdDSA, kid against AUTH_SIGNING_PUBKEY (+ previous keys), signature, exact iss/aud, sub, jti, iat, live exp, back-channel event, no nonce.
  • jti recorded first-seen; revocation by subject; retries idempotent; replay table pruned after 7 days.
  • Central mode requires a valid AUTH_SIGNING_PUBKEY at startup; bootstrap/update/docs updated. Previously central mode never received the key, so every event would have been rejected.

Tests

108 passed, ruff clean, shellcheck clean. Details in the commit message.

Deploy

Register the endpoint on Auth: auth app set-backchannel explorer https://<host>/auth/backchannel-logout. Deploy Auth (#22) first: this receiver requires exp.

…logout

TL;DR: Explorer now accepts signed logout events from the central auth service and immediately ends every Explorer session belonging to the affected account. Until now a central disable or password replacement left an already-issued Explorer session alive for up to 12 hours.

Motivation:
The central handoff (#17) created app-scoped Explorer sessions with no way for Auth to end them. The README said as much and called back-channel revocation a required follow-up.

Fix:
- POST /auth/backchannel-logout (public, unauthenticated by design: the token is the credential). Content-Length is required and capped at 20 KB before parsing.
- verify_logout_token checks typ logout+jwt, alg EdDSA, kid against AUTH_SIGNING_PUBKEY and AUTH_SIGNING_PREVIOUS_PUBKEYS, the Ed25519 signature, exact issuer and audience, sub, jti, iat not in the future, a live exp (60-second skew), the back-channel event, and rejects a nonce.
- consume_logout_event records the jti (INSERT OR IGNORE) and revokes every session for the subject only on first sight, so Auth's retries are idempotent. Replay rows are pruned after seven days. Schema version 2 migrates forward.
- Central mode now requires a valid AUTH_SIGNING_PUBKEY at startup. Bootstrap prompts for it in both modes, update.sh warns in both modes, and the docs no longer call it Elcano-only. Without this the receiver would have answered 400 to every event forever while sessions stayed alive.

Tests:
- Store: event revokes only the matching subject and is idempotent; replay table pruned after retention.
- Verifier: signature, issuer, audience, malformed issuer; expired, string, boolean, and missing exp all rejected.
- Endpoints: a signed event revokes the live cookie and a replay is still 204; oversized body is 413; central mode refuses to start without the key or with a malformed one.
- Ran: pytest -q (108 passed), ruff check, ruff format --check, bash -n and shellcheck on bootstrap.sh and update.sh.
@jzhao234
jzhao234 merged commit cd318d1 into main Sep 14, 2026
4 checks passed
@jzhao234
jzhao234 deleted the feat/backchannel-revocation branch September 14, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant