{"record":{"id":"70febd60487bad96","repo":"aaif-goose/goose","slug":"failed-to-fetch-oidc-config-configresp-status","errorCode":null,"errorMessage":"Failed to fetch OIDC config: ${configResp.status}","messagePattern":"Failed to fetch OIDC config: (.+?)","errorType":"http","errorClass":null,"httpStatus":401,"severity":"critical","filePath":"oidc-proxy/src/index.js","lineNumber":150,"sourceCode":"  return resp.json();\n}\n\n// --- OIDC JWT verification using Web Crypto API ---\n\nlet jwksCache = null;\nlet 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}","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/oidc-proxy/src/index.js#L132-L168","documentation":"Thrown by fetchJwks() in oidc-proxy when GET {issuer}/.well-known/openid-configuration returns a non-ok status. The proxy needs the discovery document to locate config.jwks_uri before it can validate tokens. A failure here means the proxy cannot verify any incoming ID token, so all authenticated requests fail.","triggerScenarios":"Calling a token-verifying path with an OIDC_ISSUER (or equivalent env) that is wrong: typo in the host, missing/extra path segment, http vs https mismatch, or a trailing slash producing a malformed well-known URL; also when the IdP is down, returns 404 for the discovery endpoint, or egress is blocked by firewall/proxy.","commonSituations":"Local testing against an issuer reachable only via VPN; Auth0/Keycloak tenant renamed so the old issuer 404s; issuer configured with the token endpoint URL instead of the base issuer; corporate proxy blocking outbound calls from the proxy process.","solutions":["curl the exact URL the code builds: curl -i \"{ISSUER}/.well-known/openid-configuration\" and confirm 200 with a jwks_uri field.","Check the issuer env var: it must be the base issuer (scheme + host [+ tenant path]), with no /oauth or /token suffix; the code strips only one trailing slash.","Verify network egress from the oidc-proxy process (DNS, proxy env vars, TLS CA bundle).","If the IdP is transiently unavailable, add retry with backoff around the discovery fetch — a cached JWKS exists (1h TTL) but the config fetch itself has none."],"exampleFix":"// before\nconst configResp = await fetch(wellKnownUrl);\nif (!configResp.ok) {\n  throw new Error(`Failed to fetch OIDC config: ${configResp.status}`);\n}\n\n// after (bounded retry + response body in the message)\nlet configResp: Response;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  configResp = await fetch(wellKnownUrl);\n  if (configResp.ok) break;\n  if (attempt === 2) {\n    const body = await configResp.text().catch(() => '');\n    throw new Error(`Failed to fetch OIDC config from ${wellKnownUrl}: ${configResp.status} ${body.slice(0, 200)}`);\n  }\n  await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));\n}","handlingStrategy":"retry","validationCode":"// Validate the issuer before first use\nfunction assertIssuerUrl(issuer: string): URL {\n  const url = new URL(issuer); // throws on malformed issuer\n  if (url.protocol !== 'https:' && url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {\n    throw new Error(`Insecure issuer scheme: ${url.protocol}`);\n  }\n  return url;\n}\n\nasync function discoveryReachable(issuer: string): Promise<boolean> {\n  const url = assertIssuerUrl(issuer);\n  const resp = await fetch(`${url.toString().replace(/\\/$/, '')}/.well-known/openid-configuration`);\n  return resp.ok;\n}","typeGuard":null,"tryCatchPattern":"async function fetchOidcConfigWithRetry(issuer: string, attempts = 3) {\n  let lastError: unknown;\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await fetchJwks(issuer);\n    } catch (error) {\n      lastError = error;\n      if (!/OIDC config/.test(String(error))) throw error; // only retry config-stage failures\n      await new Promise((r) => setTimeout(r, 500 * 2 ** i));\n    }\n  }\n  throw lastError;\n}","preventionTips":["Store the issuer, not the full endpoints; always derive URLs from discovery.","Smoke-test the well-known URL (curl) in deployment pipelines for the IdP.","Keep the 1h JWKS cache in mind: validate issuer config before deploys, since bad issuers surface up to an hour late."],"tags":["oidc","auth","network","jwks","configuration"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}