{"record":{"id":"4ff289bc719dee38","repo":"aaif-goose/goose","slug":"failed-to-fetch-jwks-jwksresp-status","errorCode":null,"errorMessage":"Failed to fetch JWKS: ${jwksResp.status}","messagePattern":"Failed to fetch JWKS: (.+?)","errorType":"http","errorClass":null,"httpStatus":401,"severity":"critical","filePath":"oidc-proxy/src/index.js","lineNumber":156,"sourceCode":"let jwksCacheTime = 0;\nconst JWKS_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\nasync function fetchJwks(issuer) {\n  const now = Date.now();\n  if (jwksCache && now - jwksCacheTime < JWKS_CACHE_TTL_MS) {\n    return jwksCache;\n  }\n\n  const wellKnownUrl = `${issuer.replace(/\\/$/, \"\")}/.well-known/openid-configuration`;\n  const configResp = await fetch(wellKnownUrl);\n  if (!configResp.ok) {\n    throw new Error(`Failed to fetch OIDC config: ${configResp.status}`);\n  }\n  const config = await configResp.json();\n\n  const jwksResp = await fetch(config.jwks_uri);\n  if (!jwksResp.ok) {\n    throw new Error(`Failed to fetch JWKS: ${jwksResp.status}`);\n  }\n\n  jwksCache = await jwksResp.json();\n  jwksCacheTime = now;\n  return jwksCache;\n}\n\nfunction base64UrlDecode(str) {\n  const padded = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n  const binary = atob(padded);\n  return Uint8Array.from(binary, (c) => c.charCodeAt(0));\n}\n\nfunction decodeJwtPart(b64url) {\n  return JSON.parse(new TextDecoder().decode(base64UrlDecode(b64url)));\n}\n\nconst ALG_MAP = {","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/oidc-proxy/src/index.js#L138-L174","documentation":"Thrown by fetchJwks() in oidc-proxy when GET config.jwks_uri returns a non-ok status after the discovery document was fetched successfully. The JWKS document holds the public keys used to verify token signatures, so this failure disables all token validation. The URL comes from the IdP's own discovery response, not from local config.","triggerScenarios":"The IdP advertises a jwks_uri that is unreachable from the proxy host: different domain blocked by egress rules, internal Keycloak hostname not resolvable externally, key-rotation endpoint temporarily 5xx, or TLS certificate mismatch on the keys endpoint.","commonSituations":"Keycloak/OpenIdP exposing an internal hostname in discovery metadata; mTLS or proxy required for the keys endpoint but not for discovery; IdP maintenance window; expired TLS cert on the jwks host while the issuer host is fine.","solutions":["curl the exact jwks_uri printed in the discovery document from the oidc-proxy host to confirm it returns 200 with a keys array.","If the IdP advertises an internal hostname, fix its advertised URL (e.g. Keycloak hostname provider / frontendUrl) or allow egress to that host.","Check TLS: certificate validity and CA trust for the jwks host specifically.","Note the 1-hour JWKS cache: after fixing the IdP, wait for cache expiry or restart the proxy; add retry-with-backoff if the keys endpoint flakes during rotation."],"exampleFix":"// before\nconst jwksResp = await fetch(config.jwks_uri);\nif (!jwksResp.ok) {\n  throw new Error(`Failed to fetch JWKS: ${jwksResp.status}`);\n}\n\n// after (log which jwks_uri failed and keep the last good cache on transient failure)\nconst jwksResp = await fetch(config.jwks_uri);\nif (!jwksResp.ok) {\n  console.error(`JWKS fetch failed: ${config.jwks_uri} -> ${jwksResp.status}`);\n  if (jwksCache && jwksResp.status >= 500) return jwksCache; // serve stale on IdP error\n  throw new Error(`Failed to fetch JWKS from ${config.jwks_uri}: ${jwksResp.status}`);\n}","handlingStrategy":"retry","validationCode":"// Verify the advertised jwks_uri resolves before serving traffic\nasync function assertJwksUriFetchable(config: { jwks_uri: string }): Promise<void> {\n  const resp = await fetch(config.jwks_uri);\n  if (!resp.ok) throw new Error(`jwks_uri ${config.jwks_uri} not reachable (${resp.status})`);\n  const body = (await resp.json()) as { keys?: unknown[] };\n  if (!Array.isArray(body.keys) || body.keys.length === 0) {\n    throw new Error(`jwks_uri ${config.jwks_uri} returned no keys`);\n  }\n}","typeGuard":"function isJwks(value: unknown): value is { keys: JsonWebKey[] } {\n  return (\n    typeof value === 'object' && value !== null &&\n    Array.isArray((value as { keys?: unknown }).keys)\n  );\n}","tryCatchPattern":"try {\n  const jwks = await fetchJwks(issuer);\n} catch (error) {\n  if (/JWKS/.test(String(error))) {\n    // Fail closed: reject tokens rather than skip verification\n    throw new Error('Token verification unavailable (JWKS fetch failed)', { cause: error });\n  }\n  throw error;\n}","preventionTips":["Configure the IdP to advertise an externally reachable jwks_uri (e.g. Keycloak hostname settings).","Monitor the keys endpoint with a health check; alert before cache expiry windows expire.","Never skip signature verification when JWKS is unavailable — fail closed."],"tags":["oidc","jwks","auth","network","key-rotation"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}