{"record":{"id":"f307e38c1c0db103","repo":"passbolt/passbolt_api","slug":"could-not-save-the-sso-state-invalid-nonce","errorCode":null,"errorMessage":"Could not save the SSO state, invalid nonce.","messagePattern":"Could not save the SSO state, invalid nonce\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesSetService.php","lineNumber":51,"sourceCode":"     * @param string $state State to store\n     * @param string $type Type of SSO state.\n     * @param string $ssoSettingsId SSO settings ID.\n     * @param \\App\\Utility\\ExtendedUserAccessControl $uac UAC object.\n     * @return \\Passbolt\\Sso\\Model\\Entity\\SsoState\n     * @throws \\Cake\\Http\\Exception\\InternalErrorException When unable to create the sso state.\n     */\n    public function create(\n        string $nonce,\n        string $state,\n        string $type,\n        string $ssoSettingsId,\n        ExtendedUserAccessControl $uac\n    ): SsoState {\n        /** @var \\Passbolt\\Sso\\Model\\Table\\SsoStatesTable $ssoStatesTable */\n        $ssoStatesTable = $this->fetchTable('Passbolt/Sso.SsoStates');\n\n        if (!SsoState::isValidState($nonce)) {\n            throw new BadRequestException(__('Could not save the SSO state, invalid nonce.'));\n        }\n\n        try {\n            $ssoState = $ssoStatesTable->newEntity(\n                [\n                    'nonce' => $nonce,\n                    'state' => $state,\n                    'type' => $type,\n                    'sso_settings_id' => $ssoSettingsId,\n                    'user_id' => $uac->getId() ?? null,\n                    'ip' => $uac->getUserIp(),\n                    'user_agent' => $uac->getUserAgent(),\n                    'deleted' => DateTime::now()->modify('+' . SsoState::getExpiryDuration()),\n                ],\n                [\n                    'accessibleFields' => [\n                        'nonce' => true,\n                        'state' => true,","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesSetService.php#L33-L69","documentation":"SsoStatesSetService::create() rejects the SSO state nonce before persisting it. SsoState::isValidState() validates the nonce format; anything not matching is treated as a tampered or malformed OAuth2 state parameter and a BadRequestException is thrown.","triggerScenarios":"Calling POST /sso/states (or create()) with a nonce that is absent, empty, not a string, or fails SsoState::isValidState() format checks (length/charset).","commonSituations":"Client generated its own state value instead of using the server-provided one; state truncated by URL/proxy; a bot or scanner hitting the endpoint with random parameters; stale frontend code after an SSO plugin upgrade changed the nonce format.","solutions":["Generate the nonce with the passbolt client SDK / server-issued state flow rather than crafting it manually","Inspect the nonce sent in the request: ensure it is a non-empty string in the exact format SsoState::isValidState() expects","Clear frontend cache/upgrade the SSO web plugin so it produces valid nonces","Log the incoming nonce (length, charset) to identify truncation or encoding issues"],"exampleFix":"// before (client hand-rolled state)\nconst nonce = Math.random().toString();\n// after (use server-generated nonce from GET /sso/states response)\nconst { nonce } = await passbolt.sso.getServerState();","handlingStrategy":"validation","validationCode":"if (!is_string($nonce) || $nonce === '' || !preg_match('/^[A-Za-z0-9\\-._~]{40,}$/', $nonce)) { throw new \\InvalidArgumentException('nonce format invalid'); }","typeGuard":"function isValidNonce(mixed $nonce): bool { return is_string($nonce) && $nonce !== ''; }","tryCatchPattern":"try { $state = $service->create($uac, $nonce); } catch (BadRequestException $e) { return $this->respondError(400, $e->getMessage()); }","preventionTips":["Always obtain the nonce from the server's SSO state endpoint","Never hand-craft or regenerate state values client-side","Log nonce length/charset when debugging SSO failures"],"tags":["sso","oauth-state","bad-request","validation"],"backgroundTag":"invalid-argument-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"}