Error message
Expected 200 OK from the JSON Web Key Set HTTP response
jose (JavaScript) — JOSEError from createRemoteJWKSet, code ERR_JOSE_GENERIC
What it means
`createRemoteJWKSet` requested the key set URL and got an HTTP status other than 200. jose fetches the JWKS with redirects disabled (`redirect: "manual"`), so a 301/302 counts as a failure just like a 404 or 401. No key was loaded and no token signature was checked; the URL you configured does not answer with a key set.
Why it happens
The configured URL is not the jwks_uri
commonEvery provider uses its own path: Auth0 `/.well-known/jwks.json`, Keycloak `/realms/<realm>/protocol/openid-connect/certs`, Entra ID `/<tenant>/discovery/v2.0/keys`, Google `https://www.googleapis.com/oauth2/v3/certs`. Pointing jose at the issuer URL or at the discovery document itself returns 404 or a non-JWKS response.
The endpoint answers with a redirect
commonHTTP-to-HTTPS redirects, a trailing-slash redirect, or a CDN that moved the file all answer 30x. Browsers and curl follow them silently; jose does not, so the URL must be the final one.
The key set is behind authentication or a network policy
occasionalInternal identity providers sometimes sit behind an API gateway that demands credentials (401/403), or an egress proxy answers with its own error page. A JWKS is public data and should be reachable without credentials from every verifier.
Too many fetches
rareA verifier that creates a new remote JWKS object per request refetches the set every time and can be throttled (429). Create the object once and reuse it so its cache works.
How to fix it
- 1.
Read jwks_uri from the discovery document
OpenID Connect providers publish the exact key set URL as `jwks_uri` in `/.well-known/openid-configuration`. Resolve it once at startup and pass that URL to `createRemoteJWKSet`.
js import { createRemoteJWKSet } from "jose"; // Resolve jwks_uri from the provider's discovery document instead of guessing it. const discovery = await fetch("https://login.example.com/.well-known/openid-configuration").then((r) => r.json()); const jwks = createRemoteJWKSet(new URL(discovery.jwks_uri)); - 2.
Check the status code without following redirects
curl without `-L` behaves like jose. A 200 with `{"keys":[...]}` is what you need; a 30x tells you the URL to configure instead.
bash # Status line and redirect target, without following redirects (jose does not follow them either) curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' https://login.example.com/jwks curl -s https://login.example.com/.well-known/openid-configuration | jq -r .jwks_uri