siyuan-note/siyuan · error

OAuth state mismatch

Error message

OAuth state mismatch

What it means

The OAuth state parameter is a random per-flow value used to bind the callback to the initiation request and prevent CSRF. When the callback's state does not equal the state generated for the flow, the library rejects the response as a possible CSRF/interception attempt rather than using the code.

Solutions

  1. Restart the authorization flow and use only a single browser tab/window for consent
  2. Do not reuse or bookmark old callback URLs; each flow has a unique state
  3. Ensure no proxy or extension rewrites query parameters on the localhost callback
  4. Clear stale tabs of the IdP consent page and retry
Defensive patterns

Strategy: retry

Try / catch

if err := h.Authorize(ctx, true); err != nil {
    if strings.Contains(err.Error(), "state mismatch") {
        // discard the stale callback and restart a fresh authorization flow
    }
}

Prevention

When it happens

Trigger: A browser request hits the local callback endpoint whose state query parameter differs from the state stored in oauthFlows for that flowID — a stale/duplicate tab, a replayed redirect, or a forged callback.

Common situations: User completed the flow twice in two tabs so one redirect hits the wrong/expired flow; a bookmarked or replayed callback URL; an attacker-crafted callback; proxy stripping/altering query parameters.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — 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/7c7aebe3e7de7b2f. Report an issue: GitHub.

Appendix: source

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

	oauthFlows.Unlock()
	defer removeOAuthFlow(flowID, flow)
	setMCPRuntimeStateForContext(ctx, h.server.ID, "authorizing", 0, "", authorizationURL)

	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

View on GitHub (pinned to 9f775e8a12)