{"record":{"id":"72a36c352dda5449","repo":"passbolt/passbolt_api","slug":"the-sso-state-is-invalid-ssostatesgetservice","errorCode":null,"errorMessage":"The SSO state is invalid.","messagePattern":"The SSO state is invalid\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesGetService.php","lineNumber":37,"sourceCode":"use Cake\\Datasource\\Exception\\RecordNotFoundException;\nuse Cake\\Http\\Exception\\BadRequestException;\nuse Cake\\ORM\\Locator\\LocatorAwareTrait;\nuse Passbolt\\Sso\\Model\\Entity\\SsoState;\n\nclass SsoStatesGetService\n{\n    use LocatorAwareTrait;\n\n    /**\n     * @param string $state State to find.\n     * @return \\Passbolt\\Sso\\Model\\Entity\\SsoState\n     * @throws \\Cake\\Datasource\\Exception\\RecordNotFoundException When given state doesn't exist or not active.\n     * @throws \\Cake\\Http\\Exception\\BadRequestException If given SSO state is invalid.\n     */\n    public function getOrFail(string $state): SsoState\n    {\n        if (!SsoState::isValidState($state)) {\n            throw new BadRequestException(__('The SSO state is invalid.'));\n        }\n\n        /** @var \\Passbolt\\Sso\\Model\\Table\\SsoStatesTable $ssoStatesTable */\n        $ssoStatesTable = $this->fetchTable('Passbolt/Sso.SsoStates');\n\n        try {\n            /** @var \\Passbolt\\Sso\\Model\\Entity\\SsoState $ssoState */\n            $ssoState = $ssoStatesTable\n                ->find('active')\n                ->where(['state' => $state])\n                ->firstOrFail();\n        } catch (RecordNotFoundException $e) {\n            throw new RecordNotFoundException(__('The SSO state does not exist.'), 400, $e);\n        }\n\n        return $ssoState;\n    }\n}","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesGetService.php#L19-L55","documentation":"Before hitting the database, SsoStatesGetService::getOrFail validates the format of the incoming state token with SsoState::isValidState(). A malformed, empty, or otherwise invalid state string yields this BadRequestException (HTTP 400) without any DB lookup. The state must match the format generated when the SSO flow was initiated.","triggerScenarios":"Calling getOrFail with a state string that fails SsoState::isValidState() — e.g. missing, empty, truncated, URL-encoded incorrectly, or tampered state parameter on the SSO callback/verify endpoint.","commonSituations":"The browser extension or client sends the callback without the state query parameter; the state gets truncated by an intermediary or copy-paste error; a security scanner or CSRF probe hits the endpoint with garbage state values; the state was double-encoded/decoded in a redirect chain.","solutions":["Inspect the callback request's state parameter — confirm it is present, complete, and not URL-encoded twice.","Restart the SSO flow to obtain a fresh, well-formed state.","Check client code constructing the callback URL that the state query parameter is preserved verbatim (no trimming/re-encoding).","Look at SsoState::isValidState() to see the accepted format and compare with what your client sends."],"exampleFix":"// before: state lost when building redirect URL\nreturn $this->redirect(\"/sso/verify?provider=$provider\");\n// after\nreturn $this->redirect('/sso/verify?' . http_build_query(['provider' => $provider, 'state' => $state]));","handlingStrategy":"validation","validationCode":"// Client-side pre-check before invoking the callback endpoint\nif (!is_string($state) || $state === '' || strlen($state) < 16) {\n    // abort: state missing/malformed, restart SSO flow\n}","typeGuard":"function isWellFormedState(mixed $state): bool {\n    return is_string($state) && preg_match('/^[A-Za-z0-9]+$/', $state) === 1 && $state !== '';\n}","tryCatchPattern":"try {\n    $ssoState = $getService->getOrFail($state);\n} catch (BadRequestException $e) {\n    // state string failed isValidState(): restart flow, do not retry same token\n}","preventionTips":["Pass the state parameter through redirects unmodified (http_build_query, no manual concatenation).","Avoid double URL-encoding in redirect chains.","Guard client code against sending undefined/null state.","Familiarize with SsoState::isValidState() to know the accepted format."],"tags":["sso","oauth-state","bad-request","input-validation","csrf"],"backgroundTag":"invalid-identifier-format","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"}