{"record":{"id":"6c8ab9eb4f367176","repo":"decolua/9router","slug":"oidc-provider-did-not-return-an-id-token","errorCode":null,"errorMessage":"OIDC provider did not return an id_token","messagePattern":"OIDC provider did not return an id_token","errorType":"exception","errorClass":"Error","httpStatus":500,"severity":"error","filePath":"src/app/api/auth/oidc/callback/route.js","lineNumber":63,"sourceCode":"    if (!config) {\n      clearOidcCookies(cookieStore);\n      return NextResponse.redirect(new URL(\"/login?error=oidc_not_configured\", getPublicOrigin(request)));\n    }\n\n    const discovery = await fetchOidcDiscovery(config.issuerUrl);\n    const discoveredIssuer = discovery.issuer || config.issuerUrl;\n    const redirectUri = `${getPublicOrigin(request)}/api/auth/oidc/callback`;\n    const tokenData = await exchangeOidcCode({\n      tokenEndpoint: discovery.token_endpoint,\n      clientId: config.clientId,\n      clientSecret: config.clientSecret,\n      code,\n      redirectUri,\n      codeVerifier,\n    });\n\n    if (!tokenData.id_token) {\n      throw new Error(\"OIDC provider did not return an id_token\");\n    }\n\n    const payload = await verifyOidcIdToken({\n      idToken: tokenData.id_token,\n      issuer: discoveredIssuer,\n      audience: config.clientId,\n      jwksUri: discovery.jwks_uri,\n      nonce: storedNonce,\n    });\n\n    clearOidcCookies(cookieStore);\n    await setDashboardAuthCookie(cookieStore, request, {\n      oidc: true,\n      oidcSub: payload.sub || null,\n      oidcEmail: pickOidcEmail(payload) || null,\n      oidcName: pickOidcDisplayName(payload),\n    });\n","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/api/auth/oidc/callback/route.js#L45-L81","documentation":"During the OIDC authorization-code callback, after exchanging the code at the provider's token endpoint, 9Router requires an id_token (it verifies issuer, audience and nonce via verifyOidcIdToken before issuing a dashboard session). If the token response has no id_token field, the callback throws this message and redirects to /login?error=OIDC+provider+did+not+return+an+id_token.","triggerScenarios":"exchangeOidcCode succeeds but the provider's token endpoint response lacks id_token — the client was configured without the openid scope, the provider only issues access tokens, or the discovery document's token_endpoint belongs to a non-OIDC OAuth2 flow.","commonSituations":"OIDC app configured without the 'openid' scope; provider is plain OAuth2 (GitHub-style) not OIDC; client registered for access-token-only grant; misconfigured issuerUrl pointing discovery at a generic OAuth2 server.","solutions":["Add 'openid' (plus profile/email) to the requested scopes in the OIDC settings — without it providers omit id_token.","Confirm the identity provider actually supports OIDC (issues ID tokens) and the issuerUrl points to its OIDC discovery document.","Check the provider's app/client configuration grants the authorization_code flow with ID tokens for this redirect URI.","Capture the token response (server logs) to verify which fields came back and confirm scope handling."],"exampleFix":"// before\nscopes: \"profile email\"\n// after\nscopes: \"openid profile email\"","handlingStrategy":"validation","validationCode":"const config = await getOidcRuntimeConfig();\nif (!config?.issuerUrl || !config?.clientId) throw new Error(\"OIDC not configured\");\nconst discovery = await fetchOidcDiscovery(config.issuerUrl);\nif (!discovery.token_endpoint) throw new Error(\"Discovery missing token_endpoint\");\nif (!/(^|\\s)openid(\\s|$)/.test(config.scopes || \"\")) throw new Error(\"openid scope required to receive an id_token\");","typeGuard":"const hasIdToken = (t) => t !== null && typeof t === \"object\" && typeof t.id_token === \"string\" && t.id_token.split(\".\").length === 3;","tryCatchPattern":"const tokenData = await exchangeOidcCode({ /* ... */ });\nif (!hasIdToken(tokenData)) {\n  return NextResponse.redirect(new URL(\"/login?error=oidc_missing_id_token_check_scopes\", getPublicOrigin(request)));\n}","preventionTips":["Always request the 'openid' scope — it is what makes providers return an id_token.","Only enable OIDC login for providers that are real OIDC IdPs, not plain OAuth2.","Verify discovery succeeds and points at an OIDC-capable token endpoint.","Encode a hint in the login redirect error so admins know to check scopes."],"tags":["oidc","authentication","configuration"],"backgroundTag":"missing-id-token","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}