{"record":{"id":"963c447e644d2839","repo":"passbolt/passbolt_api","slug":"csrf-issue-the-state-in-url-and-cookies-do-not-match","errorCode":null,"errorMessage":"CSRF issue. The state in URL and Cookies do not match.","messagePattern":"CSRF issue\\. The state in URL and Cookies do not match\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Controller/AbstractSsoController.php","lineNumber":45,"sourceCode":"use Passbolt\\Sso\\Model\\Dto\\SsoUrlResponseDto;\nuse Passbolt\\Sso\\Model\\Entity\\SsoState;\nuse Passbolt\\Sso\\Service\\Sso\\AbstractSsoService;\nuse Passbolt\\Sso\\Utility\\Validation\\OAuthTokenValidation;\n\nabstract class AbstractSsoController extends AppController\n{\n    /**\n     * Protect from CSRF by checking if state in URL and cookie matches\n     *\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the state is not provided in cookie or URL or there is a mismatch\n     * @return string\n     */\n    public function getStateFromUrlAndCookie(): string\n    {\n        $stateUrl = $this->getStateFromUrlQuery();\n        $stateCookie = $this->getStateFromCookie();\n        if ($stateUrl !== $stateCookie) {\n            throw new BadRequestException(__('CSRF issue. The state in URL and Cookies do not match.'));\n        }\n\n        return $stateUrl;\n    }\n\n    /**\n     * Protect from CSRF by checking if state in URL and cookie matches\n     *\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the state is not provided in cookie or URL or there is a mismatch\n     * @return string\n     */\n    public function getStateAndAssertAgainstCookie(): string\n    {\n        $state = $this->getRequest()->getData('state');\n        $stateCookie = $this->getStateFromCookie();\n        if ($state !== $stateCookie) {\n            throw new BadRequestException(\n                __('CSRF issue. The state in request data does not match with cookie value.')","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Controller/AbstractSsoController.php#L27-L63","documentation":"Standard CSRF protection for the SSO redirect flow: the 'state' query parameter from the provider's callback must exactly match the value stored in the SSO state cookie. A mismatch means the request was not initiated by this client (or cookies were lost), so the controller rejects it with a 400.","triggerScenarios":"GET on the SSO callback endpoint where getStateFromUrlAndCookie() compares query 'state' with the SSO_COOKIE state cookie and they differ (missing, expired, or different cookie).","commonSituations":"Browser blocking third-party/ SameSite cookies so the state cookie is absent on redirect back; opening the callback link in a different browser than the one that started the flow; proxy stripping cookies; multiple SSO tabs overwriting each other's state cookie.","solutions":["Restart the SSO flow in the same browser/tab that initiated it, with cookies enabled for the passbolt domain.","Check cookie SameSite/Secure settings — if passbolt runs behind a different domain than the IdP redirect, third-party cookie blocking may drop the state cookie.","Do not copy/paste the callback URL into another browser or incognito window.","Clear stale passbolt SSO cookies and retry; multiple concurrent SSO attempts overwrite the cookie."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const stateUrl = new URL(callbackUrl).searchParams.get('state');\nconst stateCookie = document.cookie.match(/passbolt_sso_state=([^;]+)/)?.[1];\nif (stateUrl !== decodeURIComponent(stateCookie || '')) { throw new Error('SSO state mismatch — restart flow'); }","typeGuard":"function statesMatch(urlState, cookieState) { return typeof urlState === 'string' && typeof cookieState === 'string' && urlState === cookieState; }","tryCatchPattern":"try { await completeSsoCallback(url); } catch (e) { if (e.status === 400 && /CSRF issue/.test(e.message)) { clearSsoCookies(); restartLogin(); } else { throw e; } }","preventionTips":["Keep cookies enabled and same-browser for the entire SSO flow","Never open the provider callback link in a different browser","Check SameSite/Secure cookie attributes behind proxies or different domains","Avoid running parallel SSO flows in one browser"],"tags":["csrf","sso","state-mismatch","cookies"],"backgroundTag":"csrf-state-mismatch","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}