siyuan-note/siyuan · error

OIDC response does not contain an ID token

Error message

OIDC response does not contain an ID token

What it means

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.

Solutions

  1. 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.
  2. 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.
  3. Verify the token endpoint response (via the IdP logs) actually contains id_token; check for intermediaries modifying the response.
  4. If you intended GitHub, set config.Provider to conf.OIDCProviderGitHub so the GitHub code path is used instead.

Example fix

// before
cfg := &conf.OIDC{Provider: conf.OIDCProviderCustom, IssuerURL: issuer, Scopes: []string{"email", "profile"}} // plain OAuth2 IdP, no id_token
provider, err := New(cfg, redirectURL)
// after
// use an IdP that supports OIDC, or keep scopes such that openid is honored
cfg := &conf.OIDC{Provider: conf.OIDCProviderCustom, IssuerURL: issuer, Scopes: []string{"openid", "email", "profile"}}
provider, err := New(cfg, redirectURL)
Defensive patterns

Strategy: validation

Validate before calling

hasOpenID := false
for _, s := range cfg.Scopes {
    if s == oidc.ScopeOpenID {
        hasOpenID = true
    }
}
if !hasOpenID {
    return errors.New("openid scope is required for OIDC sign-in")
}

Try / catch

claims, err := provider.Exchange(ctx, code, verifier, nonce)
if err != nil {
    if err.Error() == "OIDC response does not contain an ID token" {
        // the IdP is likely pure OAuth2; verify OIDC support before reconfiguring
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/f88fad44e703924e. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/oidc_provider/provider.go:101

func (p *Provider) AuthURL(state, nonce, codeVerifier string) string {
	if p.kind == conf.OIDCProviderGitHub {
		return p.oauth2Config.AuthCodeURL(state, oauth2.S256ChallengeOption(codeVerifier))
	}
	return p.oauth2Config.AuthCodeURL(state, oidc.Nonce(nonce), oauth2.S256ChallengeOption(codeVerifier))
}

func (p *Provider) Exchange(ctx context.Context, code, codeVerifier, nonce string) (map[string]any, error) {
	token, err := p.oauth2Config.Exchange(ctx, code, oauth2.VerifierOption(codeVerifier))
	if err != nil {
		return nil, fmt.Errorf("exchange OIDC authorization code failed: %w", err)
	}
	if p.kind == conf.OIDCProviderGitHub {
		return exchangeGitHubClaims(ctx, token)
	}
	rawIDToken, ok := token.Extra("id_token").(string)
	if !ok || rawIDToken == "" {
		return nil, errors.New("OIDC response does not contain an ID token")
	}
	idToken, err := p.verifier.Verify(ctx, rawIDToken)
	if err != nil {
		return nil, fmt.Errorf("verify OIDC ID token failed: %w", err)
	}
	if idToken.Nonce != nonce {
		return nil, errors.New("OIDC nonce does not match")
	}
	claims := map[string]any{}
	if err = idToken.Claims(&claims); err != nil {
		return nil, fmt.Errorf("decode OIDC claims failed: %w", err)
	}
	return claims, nil
}

func newGitHub(config *conf.OIDC, redirectURL string) *Provider {
	scopes := append([]string{}, config.Scopes...)
	if len(scopes) == 0 || isDefaultOIDCScopes(scopes) {

View on GitHub (pinned to 9f775e8a12)