Expand description
Keys fetched from a JWKS URL (PGRST_JWT_JWKS_URL), and kept current.
An identity provider – Supabase Auth (GoTrue), Auth0, Clerk, Cognito,
Keycloak – publishes the public keys its tokens are signed with as a JSON
Web Key Set at a well-known URL, and rotates them: a new key is published,
tokens start being signed with it, and the old one is withdrawn once no
token signed by it can still be valid. A server that read the set once, at
start-up, would refuse every token signed after the first rotation. So the
set is fetched again, on a schedule, and whenever a token names a kid
this server has never seen.
§Where the waiting happens
Verifying a token is synchronous (crate::validate_token), and stays
so: the keys a request verifies against are a snapshot taken from
JwtKeys, swapped whole when a fetch brings a new set. The only
asynchronous step is Jwks::ensure_for_token (reached through
crate::ensure_keys), which a host awaits before the synchronous
check. It reads the token’s header, and if the snapshot already holds a key
the header selects it returns at once without awaiting anything – the
common case, every request but the few after a rotation. Only an unknown
kid, or keys never loaded, or (on a host with no background task) keys
past their refresh time, make it fetch.
§Fetching
The HTTP request itself is the host’s: Fetch is implemented with
reqwest by the server and with the Workers Fetch API by the Worker, so
this crate stays free of an HTTP client and builds for wasm32. What is
fetched, and whether the answer is trusted, is decided here, once for every
host:
- HTTPS only, unless the host is loopback (
localhost,127.0.0.0/8,::1) – a development IdP, or a sidecar. A key set fetched over plain HTTP is a key set anyone on the path can replace with their own, and then sign any token they like. Checked when the URL is configured, and again for every redirect. - No redirect to another host. The URL the operator configured names
the party trusted to say which keys are valid; a redirect elsewhere is
that party delegating trust in a way nobody reviewed. A redirect on the
same host (
/jwksto/jwks/, http to https on loopback) is followed, at mostMAX_REDIRECTStimes. TheFetchimplementation must not follow redirects itself, so that this rule is the only one. FETCH_TIMEOUTandMAX_JWKS_BYTES, enforced by theFetchimplementation, which is the only party that can stop reading: a JWKS endpoint that hangs or streams forever must not hold a request, or the refresh task, hostage. Real key sets are a few kilobytes.- The body is parsed with
KeySet::from_jwk_json, exactly as a JWKS inPGRST_JWT_SECRETis. A response that is not a 200, or not a set this server can use, is a failed refresh.
§Failing
A failed refresh keeps the last good set and logs a warning: an identity
provider having a bad minute must not log every user out. If no set has
ever loaded, every token the JWKS would have verified is refused – fail
closed – and Jwks::status says so, which the server’s readiness
endpoints report as not ready. Start-up does not fail: an IdP outage that
crash-looped every replica would turn a partial outage into a total one.
§Refreshing
A successful fetch is trusted for its Cache-Control: max-age, clamped to
MIN_MAX_AGE..=MAX_MAX_AGE, or for the configured refresh interval
(PGRST_JWT_JWKS_REFRESH, default DEFAULT_REFRESH) when the response
says nothing. The provider knows how often it rotates; the clamp keeps a
max-age=0 from turning into a fetch per request and a max-age of a year
from making a withdrawn key valid for a year. A failed fetch is retried
after 1 s, 2 s, 4 s… up to MAX_RETRY (and never later than the
refresh interval).
A token whose kid is unknown triggers one fetch, at most once per
UNKNOWN_KID_INTERVAL – anyone can mint a token with a random kid,
and without the limit every such request would be a request to the
identity provider. Requests that arrive while that fetch is in flight wait
for it rather than each starting their own (natively; a Worker’s requests
never wait on one another, see Jwks::exclusive); if the kid is still
unknown afterwards the token is refused with PGRST301, as any token is
whose key this server does not hold.
§With PGRST_JWT_SECRET
Both may be set, and a token is accepted if either verifies it: the keys
in force are the union of the secret’s and the fetched set’s. That is the
deployment where a backend mints HS256 service tokens with a shared secret
while users arrive with tokens from the identity provider – Supabase’s own
legacy service_role key is exactly that. Neither source can widen the
other: a key still admits only its own algorithms, and a token naming a
kid still selects only keys carrying that kid (the plain-string secret,
which has no kid, has always accepted any, and still does).
Structs§
- Fetch
Error - A fetch or a refresh that failed, and why, for the log.
- Fetch
Response - What one
GETanswered. - Jwks
- The keys at a JWKS URL, fetched and kept current. See the module documentation.
- Jwks
Builder - Builds a
Jwks; seeJwks::builder. - Jwks
Status - What a
Jwkshas done so far: the numbers a metric or a readiness check reads. A snapshot, taken byJwks::status. - Jwks
UrlError - A JWKS URL that cannot be fetched from.
- Stored
Jwks - A fetched set, as a
JwksStorekeeps it.
Constants§
- DEFAULT_
REFRESH - How long a fetched set is used without being fetched again, when neither
the response nor
PGRST_JWT_JWKS_REFRESHsays otherwise: five minutes. - FETCH_
TIMEOUT - How long one fetch may take, connecting and reading included.
- MAX_
JWKS_ BYTES - The largest JWKS response read, in bytes: 1 MiB.
- MAX_
MAX_ AGE - The longest
Cache-Control: max-agehonoured: a day. - MAX_
REDIRECTS - How many same-host redirects are followed before the fetch is a failure.
- MAX_
RETRY - The longest wait before retrying a failed fetch.
- MIN_
MAX_ AGE - The shortest
Cache-Control: max-agehonoured. Shorter,no-cacheandno-storeall mean this. - UNKNOWN_
KID_ INTERVAL - How often a token with an unknown
kidmay cause a fetch.
Traits§
- Fetch
- An HTTP client, as much of one as fetching a JWKS needs.
- Jwks
Store - A copy of the fetched set somewhere other processes can read it.
- Maybe
Send Sync Send + Syncnatively, nothing at all on wasm32, where a Worker’s JavaScript objects are neither and nothing runs on another thread anyway.
Functions§
- check_
url - Check a JWKS URL against the rules in the module documentation:
absolute,
https, orhttpto a loopback host.