siyuan-note/siyuan · error
verify OIDC ID token failed: %w
Error message
verify OIDC ID token failed: %w
What it means
Thrown by Provider.Exchange() when p.verifier.Verify(ctx, rawIDToken) fails. The go-oidc verifier validates the JWT signature against the provider's published JWKS, checks the issuer, audience, and expiry. Any signature mismatch, expired token, wrong audience, or JWKS fetch failure is wrapped with %w.
Source
Thrown at kernel/model/oidc_provider/provider.go:105
}
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) {
scopes = []string{"read:user", "user:email"}
} else {
filtered := scopes[:0]
for _, scope := range scopes {View on GitHub (pinned to 251596fc0d)
Solutions
- Check for clock skew: ensure NTP is running on the SiYuan host; a skew of even 30 seconds can cause rejection.
- Verify the ClientID in SiYuan config matches the audience claim in the token.
- If the provider rotated signing keys, restart the SiYuan kernel to refresh the JWKS cache.
- Decode the JWT (base64) and inspect the exp, iss, aud claims manually to identify which check failed.
- Inspect the wrapped error for go-oidc's specific message (e.g., 'oidc: id token signed by unsupported algorithm').
Example fix
// before // clock skew causing verification failure // after // Sync system clock // sudo ntpdate pool.ntp.org // or install chrony/NTP daemon // Alternatively, decode JWT to inspect claims for debugging: // parts := strings.Split(rawIDToken, ".") // payload, _ := base64.RawURLEncoding.DecodeString(parts[1]) // fmt.Println(string(payload))
Defensive patterns
Strategy: try-catch
Validate before calling
// Sync system clock to prevent premature token expiry rejection // Ensure NTP is running: sudo timedatectl set-ntp true
Try / catch
claims, err := provider.Exchange(ctx, code, codeVerifier, nonce)
if err != nil {
if strings.Contains(err.Error(), "verify OIDC ID token") {
// Decode JWT to inspect claims for debugging
parts := strings.Split(rawIDToken, ".")
if len(parts) >= 2 {
payload, _ := base64.RawURLEncoding.DecodeString(parts[1])
log.Printf("ID token payload: %s", payload)
log.Printf("Verification failed — check clock skew, audience, issuer, key rotation")
}
return
}
} Prevention
- Keep the system clock synchronized (NTP/chrony) to avoid clock-skew rejection.
- Verify the ClientID matches the token's audience claim.
- Restart the kernel if the provider rotated signing keys (refreshes JWKS cache).
- Monitor for provider key rotation announcements.
When it happens
Trigger: Calling Exchange() where the id_token fails verification: the JWT signature does not match any key in the provider's JWKS, the token has expired (exp claim), the audience (aud) does not match the configured ClientID, the issuer (iss) does not match the discovery issuer, or the JWKS endpoint is unreachable.
Common situations: Clock skew between the SiYuan server and the provider causes premature expiry rejection. The provider rotated its signing keys but the cached JWKS is stale (go-oidc caches keys and may need a process restart). The ClientID used for verification differs from the one the token was issued for. The provider's clock is ahead, making the token appear expired.
Related errors
- discover OIDC provider failed: %w
- exchange OIDC authorization code failed: %w
- OIDC state is missing
- OIDC login transaction was not found or has expired
- OIDC login binding does not match
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/b4ae1cff867d2af7.
Report an issue: GitHub.