Skip to content

feat(auth): resolve Auth's signing keys from its published JWKS with static keys as fallback - #22

Merged
jzhao234 merged 2 commits into
mainfrom
feat/jwks-key-resolver
Sep 14, 2026
Merged

jzhao234 merged 2 commits into
mainfrom
feat/jwks-key-resolver

Conversation

@jzhao234

Copy link
Copy Markdown
Contributor

TLDR

Explorer verifies back-channel logout tokens with Auth's published /jwks.json keys as well as the static AUTH_SIGNING_PUBKEY. An Auth signing-key rotation no longer needs an env edit on Explorer hosts: new keys are picked up within ten minutes, or immediately when a token names an unknown kid (one refresh per minute at most). The static key remains required as bootstrap and offline fallback.

Design

AuthKeyResolver: merge static + JWKS keys (OKP/Ed25519 only), 10-minute cache, fetch failure keeps the cache, no redirects, 64 KB cap, rate-limited refresh on unknown kid. Owned by CentralAuthProvider; the receiver asks it for candidate keys per token.

Tests

Merge order, cache TTL, failure fallback, rate-limited unknown-kid refresh, malformed-entry filtering, verification with a JWKS-only key. Tests never hit the network (autouse fixture). Full suite, ruff clean.

…static keys as fallback

TL;DR: Explorer now verifies back-channel logout tokens with Auth's published key set as well as the key pasted into its environment. Rotating Auth's signing key no longer requires editing every Explorer host's env: Auth publishes the new key at /jwks.json, Explorer picks it up within ten minutes or immediately when a token names it. The static key stays as the bootstrap and offline fallback.

Problem:
Every consumer verified Auth's Ed25519 signatures with AUTH_SIGNING_PUBKEY (plus AUTH_SIGNING_PREVIOUS_PUBKEYS during rotation), so a rotation meant touching every application host in the right order or logout events started failing with 400s until the env caught up. Auth already publishes current and overlapping keys at /jwks.json; nothing read it.

Fix:
- AuthKeyResolver: static env keys plus keys parsed from <issuer>/jwks.json (OKP/Ed25519 entries only, 32-byte x, converted to the base64 form the verifier expects). Cached ten minutes; a fetch failure keeps the cached set; no redirects followed; 64 KB cap.
- keys_for_token refreshes once when a token's kid matches no known key, rate-limited to one attempt a minute so unknown-kid tokens cannot amplify requests to Auth.
- CentralAuthProvider owns one resolver; the back-channel receiver asks it for the candidate keys per token. Startup still requires one valid static key so verification works when Auth is unreachable at boot; the error message now says why.
- Tests never touch the network: an autouse fixture replaces the fetch function, and the resolver looks it up at call time.
- DEPLOYMENT.md documents the behaviour and demotes AUTH_SIGNING_PREVIOUS_PUBKEYS to a manual override.

Tests:
- New: static and published keys merge in order; the cache holds inside the TTL; a fetch failure after the TTL keeps cached keys; an unknown kid triggers exactly one refresh per minute; malformed JWKS entries (RSA, short x, non-object) are ignored; a token signed by a key present only in the JWKS verifies.
- Ran: pytest -q (full suite), ruff check, ruff format --check.
The autouse fixture that keeps tests off the network carried a leftover line that indexed __defaults__ on AuthKeyResolver.__init__, which is None now that the fetch argument defaults to None; every test errored at setup. Fixture reduced to the one monkeypatch it needs.
@jzhao234
jzhao234 merged commit 7083093 into main Sep 14, 2026
4 checks passed
@jzhao234
jzhao234 deleted the feat/jwks-key-resolver branch September 14, 2026 21:07
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