{"record":{"id":"9d5e8d1d60a42c4d","repo":"siyuan-note/siyuan","slug":"oidc-login-binding-does-not-match","errorCode":null,"errorMessage":"OIDC login binding does not match","messagePattern":"OIDC login binding does not match","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc.go","lineNumber":699,"sourceCode":"\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}\n\tif !transaction.Claimed {\n\t\ttransaction.Claimed = true\n\t\tcopy := *transaction\n\t\toidcTransactions.Unlock()\n\t\treturn &copy, false, nil\n\t}\n\tdone := transaction.Done\n\toidcTransactions.Unlock()\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, false, fmt.Errorf(\"wait for OIDC login transaction failed: %w\", ctx.Err())\n\tcase <-done:\n\t}\n\n\toidcTransactions.Lock()\n\tdefer oidcTransactions.Unlock()","sourceCodeStart":681,"sourceCodeEnd":717,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/oidc.go#L681-L717","documentation":"Transactions are bound to a per-client binding value (desktop window binding / mobile flow). claimOIDCTransaction requires the binding supplied at claim time to equal the one stored at creation, unless the desktop/validate flow explicitly allows claiming without a binding. On mismatch (or a missing binding when required) it fails with this error, preventing one browser context from hijacking another's login.","triggerScenarios":"OIDCCallback/OIDCMobileCallback presents a binding that differs from the transaction's Binding, or an empty binding for a flow that requires one (non-desktop/validate flows).","commonSituations":"Completing the login in a different browser or device than the one that started it; cookie/session lost so the binding is empty; a desktop window restarted between start and callback; proxy altering cookies.","solutions":["Complete the login in the same browser/window that initiated it","Start a new OIDC login from the client that should receive the session","Check that cookies/local storage holding the binding are not being cleared or blocked (third-party cookie settings, private mode)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// client side: refuse to open the callback without the stored binding\nif binding == \"\" {\n    return errors.New(\"missing OIDC binding; restart login from the original window\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Finish the login in the same browser/window that started it","Do not clear cookies or use private mode mid-login","Keep the desktop window alive until the login completes"],"tags":["oidc","binding","session-mismatch"],"backgroundTag":"invalid-argument-value","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"}