siyuan-note/siyuan · error
exchange OIDC authorization code failed: %w
Error message
exchange OIDC authorization code failed: %w
What it means
Thrown by Provider.Exchange() when the OAuth2 token exchange fails. After the user is redirected back with an authorization code, Exchange calls the provider's token endpoint with the code plus PKCE verifier. Any failure (invalid code, expired code, wrong redirect URI, wrong client secret, network error) is wrapped with %w.
Source
Thrown at kernel/model/oidc_provider/provider.go:94
Endpoint: discovered.Endpoint(),
RedirectURL: redirectURL,
Scopes: scopes,
},
verifier: discovered.Verifier(&oidc.Config{ClientID: config.ClientID}),
}, nil
}
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)View on GitHub (pinned to 251596fc0d)
Solutions
- Ensure the code_verifier passed to Exchange is the exact same one used to build the AuthURL (persist it server-side across the redirect).
- Check that the redirect URL is identical in both the authorization and exchange steps.
- Verify the client secret in SiYuan config matches what the provider has on record.
- Inspect the wrapped error for the provider's specific rejection reason (e.g., 'invalid_grant', 'bad_verification_code').
- If codes are expiring, reduce the latency between user consent and the callback handling.
Example fix
// before
claims, err := provider.Exchange(ctx, code, codeVerifier, nonce)
// after
// Ensure codeVerifier is the same one used in AuthURL()
storedVerifier := session.Get("oidc_code_verifier").(string)
claims, err := provider.Exchange(ctx, code, storedVerifier, nonce)
if err != nil {
log.Printf("token exchange failed (check code expiry, PKCE match, redirect URL): %v", err)
return
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate: ensure code and verifier are non-empty
if code == "" || codeVerifier == "" {
return nil, errors.New("authorization code and PKCE verifier are required")
} Try / catch
claims, err := provider.Exchange(ctx, code, codeVerifier, nonce)
if err != nil {
if strings.Contains(err.Error(), "exchange OIDC authorization code") {
// Could be expired code, PKCE mismatch, or network error
// Redirect user to re-authenticate rather than retrying the stale code
http.Redirect(w, r, "/api/oidc/login", http.StatusTemporaryRedirect)
return
}
} Prevention
- Persist the PKCE code_verifier and nonce in a session that survives the OAuth redirect.
- Ensure the redirect URL is identical in both authorization and exchange steps.
- Handle the callback promptly to avoid authorization code expiry (typically 10 minutes).
- Log the wrapped error to identify the specific provider rejection reason.
When it happens
Trigger: Calling Exchange(ctx, code, codeVerifier, nonce) where: the authorization code is expired or already-used, the code_verifier does not match the code_challenge sent during AuthURL, the redirect URL differs from the one used in the authorization request, the client secret is wrong, or the network call to the token endpoint fails.
Common situations: The user waited too long between consent and the callback (code expiry, typically 10 min). The PKCE verifier was not persisted across the redirect (e.g., stored in a session that expired). The redirect URL changed between auth and exchange (e.g., different port after restart). The client secret was rotated on the provider side but not updated in SiYuan config. Clock skew causing token endpoint to reject the request.
Related errors
- discover OIDC provider failed: %w
- OIDC redirect URL is required
- GitHub OAuth client secret is required
- OIDC response does not contain an ID token
- verify OIDC ID token failed: %w
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/08314a862fff5590.
Report an issue: GitHub.