{"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/9f775e8a12daef8255556097396f9b2739078892/kernel/model/oidc_provider/provider.go#L83-L119","documentation":"After a successful token exchange, Exchange expects the token response to include an id_token extra (per OIDC); if it is absent or empty the error is thrown. GitHub's OAuth2 flow is not OIDC and returns no ID token, which is why GitHub is special-cased before this check — reaching this error means a non-GitHub provider's token endpoint did not return an ID token.","triggerScenarios":"Calling Exchange with a custom/standard provider whose token response lacks the id_token field: the IdP is pure OAuth2 (not OIDC), the scopes did not include openid (scopes misconfigured/overridden), or a proxy stripped the response field.","commonSituations":"Configuring a plain OAuth2 server (e.g. an old GitLab or a custom OAuth service) as an OIDC provider; config.Scopes replaced the default openid scope with only email/profile; response_type or flow variant returning only an access token.","solutions":["Ensure the openid scope is included in config.Scopes; the Provider prepends it if missing, so check that scopes were not overridden downstream or that the IdP honors them.","Confirm the IdP actually implements OIDC (discovery document lists id_token signing algos / supports id_token); if it is pure OAuth2, it cannot be used with this flow.","Verify the token endpoint response (via the IdP logs) actually contains id_token; check for intermediaries modifying the response.","If you intended GitHub, set config.Provider to conf.OIDCProviderGitHub so the GitHub code path is used instead."],"exampleFix":"// before\ncfg := &conf.OIDC{Provider: conf.OIDCProviderCustom, IssuerURL: issuer, Scopes: []string{\"email\", \"profile\"}} // plain OAuth2 IdP, no id_token\nprovider, err := New(cfg, redirectURL)\n// after\n// use an IdP that supports OIDC, or keep scopes such that openid is honored\ncfg := &conf.OIDC{Provider: conf.OIDCProviderCustom, IssuerURL: issuer, Scopes: []string{\"openid\", \"email\", \"profile\"}}\nprovider, err := New(cfg, redirectURL)","handlingStrategy":"validation","validationCode":"hasOpenID := false\nfor _, s := range cfg.Scopes {\n    if s == oidc.ScopeOpenID {\n        hasOpenID = true\n    }\n}\nif !hasOpenID {\n    return errors.New(\"openid scope is required for OIDC sign-in\")\n}","typeGuard":null,"tryCatchPattern":"claims, err := provider.Exchange(ctx, code, verifier, nonce)\nif err != nil {\n    if err.Error() == \"OIDC response does not contain an ID token\" {\n        // the IdP is likely pure OAuth2; verify OIDC support before reconfiguring\n    }\n    return err\n}","preventionTips":["Confirm the IdP implements OIDC (discovery lists id_token support) before wiring it up","Never override scopes in a way that drops openid","Test sign-in with a token introspection tool to confirm id_token is returned"],"tags":["oidc","id-token","scopes","oauth2"],"backgroundTag":"unexpected-response-shape","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}