decolua/9router · error · Error
OIDC provider did not return an id_token
Error message
OIDC provider did not return an id_token
What it means
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.
Source
Thrown at src/app/api/auth/oidc/callback/route.js:63
if (!config) {
clearOidcCookies(cookieStore);
return NextResponse.redirect(new URL("/login?error=oidc_not_configured", getPublicOrigin(request)));
}
const discovery = await fetchOidcDiscovery(config.issuerUrl);
const discoveredIssuer = discovery.issuer || config.issuerUrl;
const redirectUri = `${getPublicOrigin(request)}/api/auth/oidc/callback`;
const tokenData = await exchangeOidcCode({
tokenEndpoint: discovery.token_endpoint,
clientId: config.clientId,
clientSecret: config.clientSecret,
code,
redirectUri,
codeVerifier,
});
if (!tokenData.id_token) {
throw new Error("OIDC provider did not return an id_token");
}
const payload = await verifyOidcIdToken({
idToken: tokenData.id_token,
issuer: discoveredIssuer,
audience: config.clientId,
jwksUri: discovery.jwks_uri,
nonce: storedNonce,
});
clearOidcCookies(cookieStore);
await setDashboardAuthCookie(cookieStore, request, {
oidc: true,
oidcSub: payload.sub || null,
oidcEmail: pickOidcEmail(payload) || null,
oidcName: pickOidcDisplayName(payload),
});
View on GitHub (pinned to 90b52e06ff)
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.
Example fix
// before scopes: "profile email" // after scopes: "openid profile email"
Defensive patterns
Strategy: validation
Validate before calling
const config = await getOidcRuntimeConfig();
if (!config?.issuerUrl || !config?.clientId) throw new Error("OIDC not configured");
const discovery = await fetchOidcDiscovery(config.issuerUrl);
if (!discovery.token_endpoint) throw new Error("Discovery missing token_endpoint");
if (!/(^|\s)openid(\s|$)/.test(config.scopes || "")) throw new Error("openid scope required to receive an id_token"); Type guard
const hasIdToken = (t) => t !== null && typeof t === "object" && typeof t.id_token === "string" && t.id_token.split(".").length === 3; Try / catch
const tokenData = await exchangeOidcCode({ /* ... */ });
if (!hasIdToken(tokenData)) {
return NextResponse.redirect(new URL("/login?error=oidc_missing_id_token_check_scopes", getPublicOrigin(request)));
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to load OIDC discovery document from ${discoveryUrl}
- OIDC token exchange failed (${res.status})
- [${res.status}] ${text}
- Antigravity executor not found
- Invalid baseUrl: ${override}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/6c8ab9eb4f367176.
Report an issue: GitHub.