- P2: verify_admin_token parses and enforces the JWT header (alg ==
RS256, kid present) BEFORE consulting the JWKS cache, so a
structurally invalid token costs no fetch, no lock, no provider load.
- P2: JwksCache gains a short negative-cache backoff window (5s,
time.monotonic-recorded) after a failed fetch: within it, get()
re-raises the recorded fault without touching the fetcher — and
without holding or queueing on the lock — so an outage costs one
fetch per window instead of one per request. A successful fetch
clears the backoff; nothing is cached indefinitely (TTL unchanged).
- P3: _matching_key widens the from_jwk handling to
(PyJWTError, binascii.Error, ValueError, TypeError, KeyError) so a
JWKS entry matching the kid but carrying corrupt n/e maps to the
SAME uniform 401 instead of surfacing a 500.
- P3: an unmatched kid now forces ONE TTL-bypassing refetch and
retries the match once, so rotated keys verify without waiting out
the TTL; the forced refetch is itself bounded (inside the backoff
window or the 5s kid-refetch interval the cached document is served
as-is), so unknown-kid floods cannot force a fetch per request.
- Tests: no-fetch proofs for garbage/non-RS256 tokens, corrupt-entry
401, outage fail-fast (sequential + concurrent burst), backoff
lapse/recovery, outage-as-server-fault, and the unknown-kid
refetch: one forced, throttled, resume-after-interval, rotation
picked up, failed forced refetch propagates then fails fast.
Existing tests unchanged (uniform 401 contract intact).
The admin plane had no authentication: any caller could reach its
routes once they existed. This adds the plane's ONE reusable FastAPI
dependency (require_admin_jwt in api/auth.py) that verifies an
Authentik OIDC JWT — Bearer extraction, RS256-only signature check
against the provider's JWKS, issuer, audience, required exp, and
enforced nbf — and raises the SAME 401 for every failure so the
response never leaks which check failed.
The JWKS is fetched over httpx once per app (JwksCache, 5-minute TTL,
asyncio-lock-guarded, failed fetches never cached) and tests stub the
fetcher, so the suite proves signature/expiry/audience enforcement
with locally generated RSA key pairs and zero network access.
ApiConfig grows the three required OIDC settings
(NESTQUEST_OIDC_ISSUER/AUDIENCE/JWKS_URL) under the existing fail-fast
discipline: an unconfigured issuer must not silently leave the admin
plane open, so from_env raises rather than inventing a default.
This is authentication only — the verified claims are the dependency's
return value, so the nestquest-admins group check is the next task's
addition, not a rewrite. PyJWT[crypto] joins the dev dependency group
(the API deployable's runtime manifest lands in Feature 17).
Maestro task: e70e6bfb-085a-4781-a3f4-276355bbbd19