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

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.

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 251596fc0d)

Solutions

  1. Ensure the 'openid' scope is requested in the authorization URL (the constructor does this automatically, but verify config.Scopes is not overriding).
  2. Check the OIDC provider's client/application settings to confirm ID tokens are enabled for this client.
  3. Test the token endpoint directly with curl including scope=openid to confirm the provider returns an id_token.
  4. If the provider only supports OAuth2 (not OIDC), consider using GitHub-style flow instead, or switch to a provider that supports OIDC.

Example fix

// before
// config.Scopes does not include openid and provider doesn't auto-add it

// after
// Ensure openid scope is present
scopes := config.Scopes
if !contains(scopes, oidc.ScopeOpenID) {
    scopes = append([]string{oidc.ScopeOpenID}, scopes...)
}
config.Scopes = scopes
Defensive patterns

Strategy: validation

Validate before calling

// Ensure openid scope is included so the provider issues an id_token
scopes := config.Scopes
hasOpenID := false
for _, s := range scopes {
    if s == "openid" {
        hasOpenID = true
        break
    }
}
if !hasOpenID {
    scopes = append([]string{"openid"}, scopes...)
}
config.Scopes = scopes

Type guard

func hasOpenIDScope(scopes []string) bool {
    for _, s := range scopes {
        if s == "openid" {
            return true
        }
    }
    return false
}

Try / catch

claims, err := provider.Exchange(ctx, code, codeVerifier, nonce)
if err != nil && strings.Contains(err.Error(), "does not contain an ID token") {
    // Provider did not issue an id_token — check if openid scope was granted
    log.Printf("provider returned no id_token; verify openid scope and provider OIDC support")
    return
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/f88fad44e703924e. Report an issue: GitHub.