siyuan-note/siyuan · error

OIDC state is missing

Error message

OIDC state is missing

What it means

claimOIDCTransaction validates the OAuth2 state parameter during the callback exchange. If the state query parameter is empty, there is no way to look up the login transaction, so it fails immediately with this error. The state parameter is mandatory for CSRF protection in the OIDC authorization-code flow.

Solutions

  1. Start a fresh OIDC login from SiYuan rather than reusing or manually editing the callback URL
  2. Check reverse-proxy/CDN rules so they do not strip query parameters from the callback path
  3. If a custom IdP is in use, ensure it echoes the state parameter back in the redirect

Example fix

// before: proxy strips query
proxy_pass http://kernel; # with query rewriting removing ?state=...
// after: preserve query args in the proxy
proxy_pass http://kernel; # ensure $args / $is_args$args are preserved on /api/system/oidc/callback
Defensive patterns

Strategy: validation

Validate before calling

if r.URL.Query().Get("state") == "" {
    http.Error(w, "state parameter is required", http.StatusBadRequest)
    return
}

Prevention

When it happens

Trigger: OIDCCallback or OIDCMobileCallback receives a request whose query string lacks the state parameter (state is empty string); a hand-crafted or truncated callback URL.

Common situations: The IdP drops the state parameter; the callback URL was copied/edited manually; a misconfigured redirect URL strips query parameters at the proxy.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/oidc.go:682

		}
		if transaction.Binding != "" && candidate.Binding == transaction.Binding {
			perBinding++
		}
	}
	if perIP >= oidcTransactionPerIP || perBinding >= oidcTransactionPerBind {
		return errors.New("too many pending OIDC login transactions")
	}
	oidcTransactions.byState[transaction.State] = transaction
	if transaction.PollToken != "" {
		oidcTransactions.byPoll[transaction.PollToken] = transaction.State
	}
	return nil
}

func claimOIDCTransaction(ctx context.Context, state, binding string,
	allowDesktopWithoutBinding bool) (*oidcTransaction, bool, error) {
	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")
	}

View on GitHub (pinned to 9f775e8a12)