Skip to content

Transparently recover from expired-JWT 401s (retry-on-401 + honest expiry) #97

Description

@zfarrell

Problem

A short-lived (300s) database-scoped access token reused just past its true exp can surface a spurious 401 {"error":"invalid_api_key"} to the caller, even though the credential is valid and re-minting immediately succeeds. Server-side extAuth now applies a 30s verification leeway (hotdata-dev/monopoly#1495), but the client has two gaps that should be closed independently so the SDK never propagates a recoverable auth blip:

  1. A 401 is treated as definitive and never retried. The retry classifier only retries transient 5xx/transport errors; refresh happens proactively only when the cached token is near expiry. If the cached JWT is actually expired/near-expired when a request goes out, that request 401s and the error is surfaced to the caller instead of the SDK transparently re-minting and retrying.

  2. Expiry is estimated from expires_in (exp = now + expires_in). If the mint endpoint returns a cached/stale expires_in, the cached expiry runs ahead of the JWT's true exp, so the proactive refresh fires too late and a past-exp token is sent on the wire.

Proposed change

  • On a 401 from an authenticated request, once per request: drop the cached JWT, force a fresh re-mint from the API token (not the refresh token), and retry the original request a single time. Never loop; a second 401 is a real rejection.
  • Derive the cached token's expiry from the minted JWT's own exp claim when present (fall back to expires_in). Keep — or slightly raise — the refresh margin.

Pointers

src/auth.rs: the token manager, bearer_value, the mint/refresh path, and the transient-vs-definitive retry classification.

Refs

  • Root-cause investigation: hotdata-dev/monopoly#1496
  • Server-side leeway fix: hotdata-dev/monopoly#1495
  • Prod signature: fast (~12ms) ext_authz_denied invalid_api_key on /v1/query + /v1/results, ~1/day, self-healing on next mint.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions