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
- 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.
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
- 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.
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
- Failed to fetch JWKS: ${jwksResp.status}
- TLS was requested but no TLS backend is enabled. Enable the
- Failed to receive authorization code
- Failed to exchange code: {} - {}
- Failed to exchange code: {} - {}
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/70febd60487bad96.
Report an issue: GitHub.