aaif-goose/goose · critical

Failed to fetch OIDC config: ${configResp.status}

Error message

Failed to fetch OIDC config: ${configResp.status}

What it means

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.

Source

Thrown at oidc-proxy/src/index.js:150

  return resp.json();
}

// --- OIDC JWT verification using Web Crypto API ---

let jwksCache = null;
let jwksCacheTime = 0;
const JWKS_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour

async function fetchJwks(issuer) {
  const now = Date.now();
  if (jwksCache && now - jwksCacheTime < JWKS_CACHE_TTL_MS) {
    return jwksCache;
  }

  const wellKnownUrl = `${issuer.replace(/\/$/, "")}/.well-known/openid-configuration`;
  const configResp = await fetch(wellKnownUrl);
  if (!configResp.ok) {
    throw new Error(`Failed to fetch OIDC config: ${configResp.status}`);
  }
  const config = await configResp.json();

  const jwksResp = await fetch(config.jwks_uri);
  if (!jwksResp.ok) {
    throw new Error(`Failed to fetch JWKS: ${jwksResp.status}`);
  }

  jwksCache = await jwksResp.json();
  jwksCacheTime = now;
  return jwksCache;
}

function base64UrlDecode(str) {
  const padded = str.replace(/-/g, "+").replace(/_/g, "/");
  const binary = atob(padded);
  return Uint8Array.from(binary, (c) => c.charCodeAt(0));
}

View on GitHub (pinned to 3810898a74)

Solutions

  1. curl the exact URL the code builds: curl -i "{ISSUER}/.well-known/openid-configuration" and confirm 200 with a jwks_uri field.
  2. 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.
  3. Verify network egress from the oidc-proxy process (DNS, proxy env vars, TLS CA bundle).
  4. 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.

Example fix

// before
const configResp = await fetch(wellKnownUrl);
if (!configResp.ok) {
  throw new Error(`Failed to fetch OIDC config: ${configResp.status}`);
}

// after (bounded retry + response body in the message)
let configResp: Response;
for (let attempt = 0; attempt < 3; attempt++) {
  configResp = await fetch(wellKnownUrl);
  if (configResp.ok) break;
  if (attempt === 2) {
    const body = await configResp.text().catch(() => '');
    throw new Error(`Failed to fetch OIDC config from ${wellKnownUrl}: ${configResp.status} ${body.slice(0, 200)}`);
  }
  await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate the issuer before first use
function assertIssuerUrl(issuer: string): URL {
  const url = new URL(issuer); // throws on malformed issuer
  if (url.protocol !== 'https:' && url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {
    throw new Error(`Insecure issuer scheme: ${url.protocol}`);
  }
  return url;
}

async function discoveryReachable(issuer: string): Promise<boolean> {
  const url = assertIssuerUrl(issuer);
  const resp = await fetch(`${url.toString().replace(/\/$/, '')}/.well-known/openid-configuration`);
  return resp.ok;
}

Try / catch

async function fetchOidcConfigWithRetry(issuer: string, attempts = 3) {
  let lastError: unknown;
  for (let i = 0; i < attempts; i++) {
    try {
      return await fetchJwks(issuer);
    } catch (error) {
      lastError = error;
      if (!/OIDC config/.test(String(error))) throw error; // only retry config-stage failures
      await new Promise((r) => setTimeout(r, 500 * 2 ** i));
    }
  }
  throw lastError;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/70febd60487bad96. Report an issue: GitHub.