decolua/9router · error
Failed to load OIDC discovery document from ${discoveryUrl}
Error message
Failed to load OIDC discovery document from ${discoveryUrl} What it means
fetchOidcDiscovery fetches `<issuerUrl>/.well-known/openid-configuration` with cache:no-store and throws this error whenever the response is not ok (non-2xx). The OIDC spec requires the issuer to publish its discovery document at this URL; this library throws early so callers (the /auth/oidc login flow) cannot proceed without endpoints (authorization_endpoint, token_endpoint, jwks_uri).
Source
Thrown at src/lib/auth/oidc.js:69
export async function getOidcRuntimeConfig() {
const settings = await getSettings();
if (!["oidc", "both"].includes(settings.authMode) || !isOidcConfigured(settings)) return null;
const issuerUrl = trimTrailingSlashes(settings.oidcIssuerUrl);
return {
issuerUrl,
clientId: settings.oidcClientId.trim(),
clientSecret: settings.oidcClientSecret.trim(),
scopes: normalizeScopes(settings.oidcScopes),
loginLabel: (settings.oidcLoginLabel || DEFAULT_LOGIN_LABEL).trim() || DEFAULT_LOGIN_LABEL,
};
}
export async function fetchOidcDiscovery(issuerUrl) {
const discoveryUrl = `${trimTrailingSlashes(issuerUrl)}/.well-known/openid-configuration`;
const res = await fetch(discoveryUrl, { cache: "no-store" });
if (!res.ok) {
throw new Error(`Failed to load OIDC discovery document from ${discoveryUrl}`);
}
return await res.json();
}
export function createPkcePair() {
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
return { verifier, challenge };
}
export function createOidcState() {
return crypto.randomBytes(16).toString("base64url");
}
export function createOidcNonce() {
return crypto.randomBytes(16).toString("base64url");
}
View on GitHub (pinned to 90b52e06ff)
Solutions
- Open `<issuerUrl>/.well-known/openid-configuration` in a browser/curl from the server hosting 9router; fix the configured oidcIssuerUrl until it returns JSON with 200.
- Ensure the URL includes any required path segments (Keycloak: https://host/realms/<realm>; Okta: https://org.okta.com; Auth0: https://tenant.auth0.com).
- Confirm the IdP is reachable from the server (firewall, VPN, DNS, TLS certificate validity) — a 401/403 usually means discovery is behind auth, which is not OIDC-compliant; expose it publicly.
- If the provider is OAuth2-only without discovery, use an issuer that supports OIDC or manually configure endpoints.
Example fix
// before
const res = await fetch(discoveryUrl, { cache: "no-store" });
if (!res.ok) {
throw new Error(`Failed to load OIDC discovery document from ${discoveryUrl}`);
}
// after
const res = await fetch(discoveryUrl, { cache: "no-store" });
if (!res.ok) {
throw new Error(`Failed to load OIDC discovery document from ${discoveryUrl} (HTTP ${res.status}). Check that oidcIssuerUrl is the correct issuer, e.g. https://idp.example.com/realms/main`);
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the issuer URL before calling fetchOidcDiscovery
async function assertIssuerReachable(issuerUrl) {
const url = `${issuerUrl.replace(/\/+$/, "")}/.well-known/openid-configuration`;
const res = await fetch(url, { method: "HEAD" });
if (!res.ok) throw new Error(`Issuer discovery not reachable (HTTP ${res.status}): ${url}`);
} Type guard
function isOidcDiscoveryDoc(doc) {
return !!doc && typeof doc === "object" &&
typeof doc.authorization_endpoint === "string" &&
typeof doc.token_endpoint === "string" &&
typeof doc.jwks_uri === "string";
} Try / catch
try {
const doc = await fetchOidcDiscovery(settings.oidcIssuerUrl);
} catch (err) {
if (err.message.startsWith("Failed to load OIDC discovery document")) {
// Show admin a hint: verify oidcIssuerUrl (e.g. include /realms/<realm> for Keycloak) and network reachability
}
throw err;
} Prevention
- Verify the discovery URL returns 200 JSON in a browser before saving oidcIssuerUrl in settings
- Include required issuer path segments (Keycloak realms, Auth0/Okta domains)
- Confirm the IdP is reachable from the server, not just your laptop (firewalls/VPN)
- Use the settings 'test connection' / probe endpoints before enabling OIDC auth mode
When it happens
Trigger: Any login flow that calls fetchOidcDiscovery(issuerUrl) where the discovery URL returns 404 (issuer URL wrong or missing path segment, e.g. missing realm for Keycloak), 401/403 (endpoint protected), 5xx (IdP outage), or a redirect-to-HTML that fetch resolves with a non-ok status.
Common situations: Misconfigured oidcIssuerUrl in settings (typo, http vs https, missing tenant/realm path like `/realms/master` for Keycloak or `/realms/<tenant>` for Auth0 needs the Auth0 domain not the issuer-with-path style), issuer behind a firewall/VPN unreachable from the 9router server, IdP serving discovery only on the exact issuer URL (trailing-slash or case mismatch), or using an OAuth2-only provider that has no OIDC discovery endpoint.
Related errors
- Failed to fetch image: ${res.status}
- Google translate fetch failed: ${res.status}
- Google TTS failed: ${res.status}
- MiniMax TTS error (${res.status})
- loadCodeAssist failed: HTTP ${response.status} ${errorText.s
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/3fbfa0386cb06e73.
Report an issue: GitHub.