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

  1. Inspect the wrapped cause for the exact OAuth error (invalid_grant, invalid_client, ...) and fix accordingly
  2. Perform a fresh full authorization instead of reusing/replaying the code — codes are single-use
  3. Verify the registered token_endpoint_auth_method matches how the server expects the client to authenticate
  4. Check network reachability of the token endpoint and system clock accuracy
  5. 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

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


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)