{"record":{"id":"47089e6b3dbee950","repo":"siyuan-note/siyuan","slug":"oidc-state-is-missing","errorCode":null,"errorMessage":"OIDC state is missing","messagePattern":"OIDC state is missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc.go","lineNumber":682,"sourceCode":"\t\t}\n\t\tif transaction.Binding != \"\" && candidate.Binding == transaction.Binding {\n\t\t\tperBinding++\n\t\t}\n\t}\n\tif perIP >= oidcTransactionPerIP || perBinding >= oidcTransactionPerBind {\n\t\treturn errors.New(\"too many pending OIDC login transactions\")\n\t}\n\toidcTransactions.byState[transaction.State] = transaction\n\tif transaction.PollToken != \"\" {\n\t\toidcTransactions.byPoll[transaction.PollToken] = transaction.State\n\t}\n\treturn nil\n}\n\nfunc claimOIDCTransaction(ctx context.Context, state, binding string,\n\tallowDesktopWithoutBinding bool) (*oidcTransaction, bool, error) {\n\tif state == \"\" {\n\t\treturn nil, false, errors.New(\"OIDC state is missing\")\n\t}\n\toidcTransactions.Lock()\n\tcleanupOIDCTransactionsLocked()\n\ttransaction := oidcTransactions.byState[state]\n\tif transaction == nil {\n\t\toidcTransactions.Unlock()\n\t\treturn nil, false, errors.New(\"OIDC login transaction was not found or has expired\")\n\t}\n\tif transaction.ConfigVersion != oidcConfigurationVersion(Conf.GetOIDC()) {\n\t\tdeleteOIDCTransactionLocked(state)\n\t\toidcTransactions.Unlock()\n\t\treturn nil, false, errors.New(\"OIDC configuration changed during login\")\n\t}\n\tif !(allowDesktopWithoutBinding && (transaction.Flow == oidcFlowDesktop || transaction.Flow == oidcFlowValidate)) &&\n\t\t(binding == \"\" || binding != transaction.Binding) {\n\t\toidcTransactions.Unlock()\n\t\treturn nil, false, errors.New(\"OIDC login binding does not match\")\n\t}","sourceCodeStart":664,"sourceCodeEnd":700,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/oidc.go#L664-L700","documentation":"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.","triggerScenarios":"OIDCCallback or OIDCMobileCallback receives a request whose query string lacks the state parameter (state is empty string); a hand-crafted or truncated callback URL.","commonSituations":"The IdP drops the state parameter; the callback URL was copied/edited manually; a misconfigured redirect URL strips query parameters at the proxy.","solutions":["Start a fresh OIDC login from SiYuan rather than reusing or manually editing the callback URL","Check reverse-proxy/CDN rules so they do not strip query parameters from the callback path","If a custom IdP is in use, ensure it echoes the state parameter back in the redirect"],"exampleFix":"// before: proxy strips query\nproxy_pass http://kernel; # with query rewriting removing ?state=...\n// after: preserve query args in the proxy\nproxy_pass http://kernel; # ensure $args / $is_args$args are preserved on /api/system/oidc/callback","handlingStrategy":"validation","validationCode":"if r.URL.Query().Get(\"state\") == \"\" {\n    http.Error(w, \"state parameter is required\", http.StatusBadRequest)\n    return\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never hand-edit the callback URL; always follow the IdP redirect","Verify reverse proxy/CDN preserves query strings on /api/system/oidc/callback","Confirm the IdP echoes the state parameter back"],"tags":["oidc","csrf","missing-parameter"],"backgroundTag":"missing-required-argument","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}