siyuan-note/siyuan · error

OAuth callback did not include an authorization code

Error message

OAuth callback did not include an authorization code

What it means

The authorization-code flow requires the redirect to include a code query parameter. The callback handler passes the parsed result to Authorize; if the state matched but no code is present, there is nothing to exchange and the library aborts with this error.

Solutions

  1. Retry the authorization; transient IdP redirects without a code usually succeed on a second attempt
  2. Verify the authorization server issues response_type=code redirects correctly (test with another OAuth client)
  3. Check any local proxy/AV software that may strip query strings from 127.0.0.1 callbacks
  4. If it persists, inspect the callback URL the IdP produced (browser dev tools network log) and compare with the expected format
Defensive patterns

Strategy: retry

Try / catch

if err := h.Authorize(ctx, true); err != nil {
    if strings.Contains(err.Error(), "did not include an authorization code") {
        // retry the flow; inspect the IdP redirect if it recurs
    }
}

Prevention

When it happens

Trigger: The browser callback reached the local endpoint with the correct state but an empty/missing code parameter — e.g. the IdP redirected without issuing a code, or the code was stripped in transit.

Common situations: IdP misconfiguration where the redirect is performed without the authorization code; a proxy or middleware dropping query parameters; user landing on a redirect URL variant that omits code (e.g. error path handled only via state).

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/662c77e0960ff2cc. Report an issue: GitHub.

Appendix: source

Thrown at kernel/mcp/client/oauth.go:392

	var callback oauthCallbackResult
	timer := time.NewTimer(oauthAuthorizationTimeout)
	defer timer.Stop()
	select {
	case callback = <-flow.Result:
	case <-ctx.Done():
		return ctx.Err()
	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

View on GitHub (pinned to 9f775e8a12)