{"record":{"id":"f88fad44e703924e","repo":"siyuan-note/siyuan","slug":"oidc-response-does-not-contain-an-id-token","errorCode":null,"errorMessage":"OIDC response does not contain an ID token","messagePattern":"OIDC response does not contain an ID token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc_provider/provider.go","lineNumber":101,"sourceCode":"\nfunc (p *Provider) AuthURL(state, nonce, codeVerifier string) string {\n\tif p.kind == conf.OIDCProviderGitHub {\n\t\treturn p.oauth2Config.AuthCodeURL(state, oauth2.S256ChallengeOption(codeVerifier))\n\t}\n\treturn p.oauth2Config.AuthCodeURL(state, oidc.Nonce(nonce), oauth2.S256ChallengeOption(codeVerifier))\n}\n\nfunc (p *Provider) Exchange(ctx context.Context, code, codeVerifier, nonce string) (map[string]any, error) {\n\ttoken, err := p.oauth2Config.Exchange(ctx, code, oauth2.VerifierOption(codeVerifier))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"exchange OIDC authorization code failed: %w\", err)\n\t}\n\tif p.kind == conf.OIDCProviderGitHub {\n\t\treturn exchangeGitHubClaims(ctx, token)\n\t}\n\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\tif !ok || rawIDToken == \"\" {\n\t\treturn nil, errors.New(\"OIDC response does not contain an ID token\")\n\t}\n\tidToken, err := p.verifier.Verify(ctx, rawIDToken)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"verify OIDC ID token failed: %w\", err)\n\t}\n\tif idToken.Nonce != nonce {\n\t\treturn nil, errors.New(\"OIDC nonce does not match\")\n\t}\n\tclaims := map[string]any{}\n\tif err = idToken.Claims(&claims); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode OIDC claims failed: %w\", err)\n\t}\n\treturn claims, nil\n}\n\nfunc newGitHub(config *conf.OIDC, redirectURL string) *Provider {\n\tscopes := append([]string{}, config.Scopes...)\n\tif len(scopes) == 0 || isDefaultOIDCScopes(scopes) {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/oidc_provider/provider.go#L83-L119","documentation":"Thrown by Provider.Exchange() when the token response does not include an id_token field. For standard OIDC providers (non-GitHub), the id_token is the JWT carrying user identity claims; its absence means the provider did not issue an ID token, which defeats the purpose of OIDC. The code type-asserts token.Extra(\"id_token\") to string and checks for empty.","triggerScenarios":"Calling Exchange() against a provider that returned a valid OAuth2 access token but no id_token. This happens when: the openid scope was not included in the authorization request, the provider is configured as a plain OAuth2 provider without OIDC support, or the provider requires additional configuration to issue ID tokens.","commonSituations":"The scopes list did not include 'openid' (though the constructor auto-prepends it for non-GitHub, a misconfiguration or custom scope override could drop it). The provider (e.g., an older Keycloak realm, or Azure AD app registration without 'id_tokens' enabled in the manifest) does not issue ID tokens for this client. The response was intercepted/modified by a proxy stripping fields.","solutions":["Ensure the 'openid' scope is requested in the authorization URL (the constructor does this automatically, but verify config.Scopes is not overriding).","Check the OIDC provider's client/application settings to confirm ID tokens are enabled for this client.","Test the token endpoint directly with curl including scope=openid to confirm the provider returns an id_token.","If the provider only supports OAuth2 (not OIDC), consider using GitHub-style flow instead, or switch to a provider that supports OIDC."],"exampleFix":"// before\n// config.Scopes does not include openid and provider doesn't auto-add it\n\n// after\n// Ensure openid scope is present\nscopes := config.Scopes\nif !contains(scopes, oidc.ScopeOpenID) {\n    scopes = append([]string{oidc.ScopeOpenID}, scopes...)\n}\nconfig.Scopes = scopes","handlingStrategy":"validation","validationCode":"// Ensure openid scope is included so the provider issues an id_token\nscopes := config.Scopes\nhasOpenID := false\nfor _, s := range scopes {\n    if s == \"openid\" {\n        hasOpenID = true\n        break\n    }\n}\nif !hasOpenID {\n    scopes = append([]string{\"openid\"}, scopes...)\n}\nconfig.Scopes = scopes","typeGuard":"func hasOpenIDScope(scopes []string) bool {\n    for _, s := range scopes {\n        if s == \"openid\" {\n            return true\n        }\n    }\n    return false\n}","tryCatchPattern":"claims, err := provider.Exchange(ctx, code, codeVerifier, nonce)\nif err != nil && strings.Contains(err.Error(), \"does not contain an ID token\") {\n    // Provider did not issue an id_token — check if openid scope was granted\n    log.Printf(\"provider returned no id_token; verify openid scope and provider OIDC support\")\n    return\n}","preventionTips":["Always include the 'openid' scope in the authorization request.","Verify the provider supports OIDC (not just OAuth2) before configuring it.","Test the token endpoint directly to confirm id_token is returned."],"tags":["oidc","authentication","id-token","oauth2","scopes"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}