You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(replay): single-use claim on consent + callback state (#22)
Closes the only documented security trade-off in specs.md
(consent_token replay) and removes IdP-fan-out + audit noise on
retried /callback. When a replay store is wired (REDIS_URL):
- GET /authorize seals a fresh JTI on every consent render and
a fresh SessionID on every silent-redirect session. Per-render
JTI keeps back-button safe (a re-render gets a new claim slot)
while still single-use within one render.
- POST /consent ClaimOnce on JTI before either approve or deny —
a captured consent_token cannot be replayed for either decision.
- GET /callback ClaimOnce on session.SessionID before the upstream
OIDC exchange — a replayed /callback URL never fans out to the
IdP token endpoint and never produces audit-log noise.
- Replay detected → 400 invalid_request with error_code
consent_replay / callback_state_replay, increments
mcp_auth_replay_detected_total{kind=consent|callback_state}.
- Store unreachable → 503 with error_code replay_store_unavailable,
increments mcp_auth_access_denied_total{reason="replay_store_unavailable"}
(reuses the existing /token counter so one alert rule covers
every claim site).
- nil store → stateless fallback (configured opt-out, mirrors /token).
- Empty JTI / SessionID on tokens sealed by older binaries falls
through to stateless behavior so an in-flight rollout doesn't
503 active users for the duration of consentTTL / sessionTTL.
- The /callback IdP-error redirect path deliberately does NOT
claim — the redirect is idempotent (no IdP fan-out, no token
issuance) and claiming would force re-auth on every legit
transient-error retry.
- All four replay_store_error log sites now carry an `op` field
(claim_authz_code / claim_refresh_family / claim_consent /
claim_callback_state) for site-level log correlation.
specs.md and README updated. 10 new handler tests cover replay
detection, deny-replayed, nil-store fallback, legacy empty
JTI/SID, store-error fail-closed, and JTI/SID population by
/authorize for both forks.
0 commit comments