siyuan-note/siyuan · error

OIDC login binding does not match

Error message

OIDC login binding does not match

What it means

The binding supplied at claim differs from the binding stored on the transaction, and the desktop/validate-without-binding exemption does not apply. The binding ties a state to the session/device that started it, so a mismatch blocks session hijack or replay of a state into another session.

Source

Thrown at kernel/model/oidc.go:755

	if state == "" {
		return nil, false, errors.New("OIDC state is missing")
	}
	oidcTransactions.Lock()
	cleanupOIDCTransactionsLocked()
	transaction := oidcTransactions.byState[state]
	if transaction == nil {
		oidcTransactions.Unlock()
		return nil, false, errors.New("OIDC login transaction was not found or has expired")
	}
	if transaction.ConfigVersion != oidcConfigurationVersion(Conf.GetOIDC()) {
		deleteOIDCTransactionLocked(state)
		oidcTransactions.Unlock()
		return nil, false, errors.New("OIDC configuration changed during login")
	}
	if !(allowDesktopWithoutBinding && (transaction.Flow == oidcFlowDesktop || transaction.Flow == oidcFlowValidate)) &&
		(binding == "" || binding != transaction.Binding) {
		oidcTransactions.Unlock()
		return nil, false, errors.New("OIDC login binding does not match")
	}
	if !transaction.Claimed {
		transaction.Claimed = true
		copy := *transaction
		oidcTransactions.Unlock()
		return &copy, false, nil
	}
	done := transaction.Done
	oidcTransactions.Unlock()

	select {
	case <-ctx.Done():
		return nil, false, fmt.Errorf("wait for OIDC login transaction failed: %w", ctx.Err())
	case <-done:
	}

	oidcTransactions.Lock()
	defer oidcTransactions.Unlock()

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restart the flow in a single browser profile and keep cookies stable through the whole flow.
  2. Avoid clearing cookies between start and callback.
  3. Confirm the client sends the same binding from start through poll/callback.
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the binding that will be presented at claim matches the one captured at start.
if startBinding != expectedBinding {
    return errors.New("binding mismatch - restart the flow in one session")
}

Try / catch

// On binding mismatch, restart the flow instead of retrying with a different session.
if err != nil && strings.Contains(err.Error(), "binding does not match") {
    restartOIDCFlow(c)
}

Prevention

When it happens

Trigger: Poll or callback where the binding cookie was cleared or rotated between start and claim, or a different session attempts to consume a state.

Common situations: User cleared cookies mid-login; browser split across profiles/containers; cross-tab interference; an attempt to replay a captured state in another session.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/9d5e8d1d60a42c4d. Report an issue: GitHub.