{"record":{"id":"3fbfa0386cb06e73","repo":"decolua/9router","slug":"failed-to-load-oidc-discovery-document-from-disc","errorCode":null,"errorMessage":"Failed to load OIDC discovery document from ${discoveryUrl}","messagePattern":"Failed to load OIDC discovery document from (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/auth/oidc.js","lineNumber":69,"sourceCode":"export async function getOidcRuntimeConfig() {\n  const settings = await getSettings();\n  if (![\"oidc\", \"both\"].includes(settings.authMode) || !isOidcConfigured(settings)) return null;\n\n  const issuerUrl = trimTrailingSlashes(settings.oidcIssuerUrl);\n  return {\n    issuerUrl,\n    clientId: settings.oidcClientId.trim(),\n    clientSecret: settings.oidcClientSecret.trim(),\n    scopes: normalizeScopes(settings.oidcScopes),\n    loginLabel: (settings.oidcLoginLabel || DEFAULT_LOGIN_LABEL).trim() || DEFAULT_LOGIN_LABEL,\n  };\n}\n\nexport async function fetchOidcDiscovery(issuerUrl) {\n  const discoveryUrl = `${trimTrailingSlashes(issuerUrl)}/.well-known/openid-configuration`;\n  const res = await fetch(discoveryUrl, { cache: \"no-store\" });\n  if (!res.ok) {\n    throw new Error(`Failed to load OIDC discovery document from ${discoveryUrl}`);\n  }\n  return await res.json();\n}\n\nexport function createPkcePair() {\n  const verifier = crypto.randomBytes(32).toString(\"base64url\");\n  const challenge = crypto.createHash(\"sha256\").update(verifier).digest(\"base64url\");\n  return { verifier, challenge };\n}\n\nexport function createOidcState() {\n  return crypto.randomBytes(16).toString(\"base64url\");\n}\n\nexport function createOidcNonce() {\n  return crypto.randomBytes(16).toString(\"base64url\");\n}\n","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/auth/oidc.js#L51-L87","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst res = await fetch(discoveryUrl, { cache: \"no-store\" });\nif (!res.ok) {\n  throw new Error(`Failed to load OIDC discovery document from ${discoveryUrl}`);\n}\n// after\nconst res = await fetch(discoveryUrl, { cache: \"no-store\" });\nif (!res.ok) {\n  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`);\n}","handlingStrategy":"validation","validationCode":"// Validate the issuer URL before calling fetchOidcDiscovery\nasync function assertIssuerReachable(issuerUrl) {\n  const url = `${issuerUrl.replace(/\\/+$/, \"\")}/.well-known/openid-configuration`;\n  const res = await fetch(url, { method: \"HEAD\" });\n  if (!res.ok) throw new Error(`Issuer discovery not reachable (HTTP ${res.status}): ${url}`);\n}","typeGuard":"function isOidcDiscoveryDoc(doc) {\n  return !!doc && typeof doc === \"object\" &&\n    typeof doc.authorization_endpoint === \"string\" &&\n    typeof doc.token_endpoint === \"string\" &&\n    typeof doc.jwks_uri === \"string\";\n}","tryCatchPattern":"try {\n  const doc = await fetchOidcDiscovery(settings.oidcIssuerUrl);\n} catch (err) {\n  if (err.message.startsWith(\"Failed to load OIDC discovery document\")) {\n    // Show admin a hint: verify oidcIssuerUrl (e.g. include /realms/<realm> for Keycloak) and network reachability\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["oidc","http","configuration","network"],"backgroundTag":"oidc-discovery-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}