siyuan-note/siyuan · error
exchange OAuth authorization code
Error message
exchange OAuth authorization code: %w
What it means
After receiving the authorization code, the library exchanges it at the token endpoint using PKCE (verifier) and the resource parameter. Any failure of the underlying oauth2 Config.Exchange call (network error, invalid_grant, invalid_client, expired code, etc.) is wrapped as 'exchange OAuth authorization code: <cause>'.
Solutions
- Inspect the wrapped cause for the exact OAuth error (invalid_grant, invalid_client, ...) and fix accordingly
- Perform a fresh full authorization instead of reusing/replaying the code — codes are single-use
- Verify the registered token_endpoint_auth_method matches how the server expects the client to authenticate
- Check network reachability of the token endpoint and system clock accuracy
- Confirm the resource parameter/audience configured on the server matches the protected resource
Defensive patterns
Strategy: retry
Validate before calling
// Confirm the token endpoint is reachable before starting the flow
resp, err := h.client.Head(asm.TokenEndpoint)
if err != nil { /* token endpoint unreachable: fix network first */ } Try / catch
if err := h.Authorize(ctx, true); err != nil {
if strings.Contains(err.Error(), "exchange OAuth authorization code") {
var retrieveErr *oauth2.RetrieveError
if errors.As(err, &retrieveErr) {
// inspect retrieveErr.ErrorCode (invalid_grant, invalid_client, ...)
}
}
} Prevention
- Never replay authorization codes; run a full fresh flow each time
- Keep the registered token_endpoint_auth_method consistent with server expectations
- Keep system clocks accurate (code/token lifetime validation)
- Confirm the resource/audience indicator matches the protected resource configuration
When it happens
Trigger: config.Exchange(exchangeCtx, callback.Code, VerifierOption, SetAuthURLParam(resource)) returns an error — e.g. HTTP 400/401 from the token endpoint, network failure, or context cancellation during the request.
Common situations: Authorization code expired or already redeemed (invalid_grant) after a retry; client authentication rejected because the registered method (basic/post/none) doesn't match the server's expectation; clock skew; the resource indicator mismatches the server audience; token endpoint unreachable.
Related errors
- OAuth authorization server does not support PKCE S256
- configuration is not initialized
- discover OAuth authorization server
- mcp oauth authorization required
- OAuth authorization failed
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/38a8024569b58e03.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/client/oauth.go:400
case <-timer.C:
return fmt.Errorf("OAuth authorization timed out")
}
if callback.Error != "" {
return fmt.Errorf("OAuth authorization failed: %s", callback.Error)
}
if callback.State != state {
return fmt.Errorf("OAuth state mismatch")
}
if callback.Code == "" {
return fmt.Errorf("OAuth callback did not include an authorization code")
}
exchangeCtx := context.WithValue(ctx, oauth2.HTTPClient, h.client)
token, err := config.Exchange(exchangeCtx, callback.Code,
oauth2.VerifierOption(verifier),
oauth2.SetAuthURLParam("resource", prm.Resource))
if err != nil {
return fmt.Errorf("exchange OAuth authorization code: %w", err)
}
if token.TokenType != "" && !strings.EqualFold(token.TokenType, "Bearer") {
return fmt.Errorf("OAuth token endpoint returned unsupported token type %q", token.TokenType)
}
credential = registrationCredential
credential.TokenAuthMethod = authMethod
credential.AccessToken = token.AccessToken
credential.RefreshToken = token.RefreshToken
credential.TokenType = token.TokenType
credential.Expiry = token.Expiry
credential.Scopes = scopes
credential.Rejected = false
if err = putOAuthCredential(credential); err != nil {
return fmt.Errorf("save OAuth credentials: %w", err)
}
h.sourceMu.Lock()
h.source = &storedOAuthTokenSource{credential: credential, client: h.client}
h.sourceMu.Unlock()View on GitHub (pinned to 9f775e8a12)