{"record":{"id":"dd2876b98bc6200f","repo":"passbolt/passbolt_api","slug":"csrf-issue-the-state-in-request-data-does-not-match-with","errorCode":null,"errorMessage":"CSRF issue. The state in request data does not match with cookie value.","messagePattern":"CSRF issue\\. The state in request data does not match with cookie value\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Controller/AbstractSsoController.php","lineNumber":62,"sourceCode":"        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.')\n            );\n        }\n\n        return $state;\n    }\n\n    /**\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the state is not provided in cookie or invalid type\n     * @return string state\n     */\n    public function getStateFromCookie(): string\n    {\n        $state = $this->request->getCookie(AbstractSsoService::SSO_STATE_COOKIE);\n        if (!is_string($state) || !SsoState::isValidState($state)) {\n            throw new BadRequestException(__('The state is required in cookie.'));\n        }\n","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Controller/AbstractSsoController.php#L44-L80","documentation":"CSRF check for the POST-based SSO endpoints: the 'state' field in the request body must match the state stored in the SSO cookie. The controller compares them and throws a 400 on mismatch, preventing forged or replayed SSO requests.","triggerScenarios":"POST to an SSO endpoint (e.g. stage2 code verification via request data) where getData('state') differs from getStateFromCookie(), including when the cookie is absent or the client omits/mistypes the state field.","commonSituations":"API client sending state from an old session while holding a newer cookie; missing state field in the JSON body; test harness that sets the cookie but not the payload state (or vice versa); two parallel SSO flows in one browser.","solutions":["Ensure the request body's 'state' field is taken from the same value issued in the SSO_COOKIE at flow start.","Restart the flow so the cookie and the request data come from the same attempt.","In automated clients, persist the state value issued in stage1 and send it verbatim in stage2.","Verify Content-Type and body encoding so getData('state') actually parses (JSON vs form data)."],"exampleFix":"// before\nawait fetch('/sso/stage2', {method:'POST', body: JSON.stringify({code})});\n// after\nawait fetch('/sso/stage2', {method:'POST', body: JSON.stringify({code, state: savedState})});","handlingStrategy":"validation","validationCode":"const state = issuedStateAtStage1;\nif (typeof state !== 'string' || state.length === 0) { throw new Error('State not captured at stage1'); }\npayload.state = state; // send verbatim in stage2 body","typeGuard":"function hasStateInBody(body, cookieState) { return typeof body?.state === 'string' && body.state === cookieState; }","tryCatchPattern":"try { await api.post('/sso/stage2', {code, state}); } catch (e) { if (e.status === 400 && /state in request data/.test(e.message)) { restartSsoFlow(); } else { throw e; } }","preventionTips":["Persist the state issued at flow start and echo it in every subsequent request","Send JSON bodies with Content-Type: application/json","Run only one SSO flow per browser session","Regenerate state after any cookie change"],"tags":["csrf","sso","state-mismatch","request-validation"],"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"}