{"record":{"id":"93c5f9aadb52e193","repo":"passbolt/passbolt_api","slug":"the-sso-state-is-invalid-settings-mismatch","errorCode":null,"errorMessage":"The SSO state is invalid. Settings mismatch.","messagePattern":"The SSO state is invalid\\. Settings mismatch\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesAssertService.php","lineNumber":113,"sourceCode":"\n        if ($ssoState->user_id !== $uac->getId() || !Validation::uuid($ssoState->user_id)) {\n            throw new BadRequestException($errorMsg . __('User id mismatch.'));\n        }\n\n        if (Configure::read('passbolt.security.userIp')) {\n            if ($ssoState->ip !== $uac->getUserIp()) {\n                throw new BadRequestException($errorMsg . __('User IP mismatch.'));\n            }\n        }\n\n        if (Configure::read('passbolt.security.userAgent')) {\n            if ($ssoState->user_agent !== $uac->getUserAgent()) {\n                throw new BadRequestException($errorMsg . __('User agent mismatch.'));\n            }\n        }\n\n        if ($ssoState->sso_settings_id !== $ssoSettingsId || !Validation::uuid($ssoState->sso_settings_id)) {\n            throw new BadRequestException($errorMsg . __('Settings mismatch.'));\n        }\n    }\n\n    /**\n     * Same assertions but without user ID.\n     *\n     * @param \\Passbolt\\Sso\\Model\\Entity\\SsoState $ssoState SSO state entity.\n     * @param string $ssoSettingsId SSO Settings ID.\n     * @param \\App\\Utility\\ExtendedUserAccessControl $uac UAC object.\n     * @return void\n     */\n    private function assertWithoutUser(SsoState $ssoState, string $ssoSettingsId, ExtendedUserAccessControl $uac): void\n    {\n        $errorMsg = __('The SSO state is invalid.') . ' ';\n\n        if (!SsoState::isValidState($ssoState->state)) {\n            throw new BadRequestException(trim($errorMsg));\n        }","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesAssertService.php#L95-L131","documentation":"The SSO state record stores which SSO settings (organization provider configuration) it was created against. assert() throws BadRequestException when the state's sso_settings_id does not equal the ssoSettingsId passed to the assertion, or when the stored id is not a valid UUID. This guarantees a state created for one SSO configuration cannot be exchanged under another.","triggerScenarios":"assertAndConsume() -> assert() where $ssoState->sso_settings_id !== $ssoSettingsId or !Validation::uuid($ssoState->sso_settings_id). Typically the SSO settings were edited/deleted/re-created (generating a new settings UUID) between state creation and callback, or the wrong settings id is passed to the assertion.","commonSituations":"Admin re-saving or switching SSO provider settings while users have in-flight SSO flows; expired/stale state rows from before a settings rotation; environment data restored from a backup mixing old settings ids; calling SsoStatesAssertService with the wrong settings id in custom code or tests; multi-org (org-to-org) flows referencing a settings id that no longer exists.","solutions":["Have the user restart the SSO login flow so a fresh state is created against the current settings id","Verify the SSO settings were not edited, deleted, or re-created mid-flow (check sso_settings table created/modified timestamps)","Confirm the ssoSettingsId passed into assertAndConsume/assert is the current settings record's UUID and is valid","Purge stale sso_states rows referencing old/deleted settings ids","Check for environment/backup restores that desynchronized sso_states and sso_settings tables"],"exampleFix":"// before: resuming a stale flow with a settings id captured earlier\n$ssoSettingsId = $state->sso_settings_id; // stale, settings were re-created\n$service->assertAndConsume($state, $ssoSettingsId, $uac);\n// after: re-resolve the current settings before asserting\n$settings = $this->SsoSettings->getActiveSettings();\n$service->assertAndConsume($state, $settings->id, $uac); // fresh state must also be created for these settings","handlingStrategy":"validation","validationCode":"// validate the settings id before asserting\nif (!Validation::uuid($ssoSettingsId) || $ssoState->sso_settings_id !== $ssoSettingsId) {\n    // abandon state and restart the SSO flow against current settings\n}","typeGuard":"// PHP\nfunction isValidSettingsId(string $id): bool\n{\n    return Validation::uuid($id);\n}","tryCatchPattern":"try {\n    $this->ssoStatesAssertService->assertAndConsume($ssoState, $ssoSettingsId, $uac);\n} catch (BadRequestException $e) {\n    if (str_contains($e->getMessage(), 'Settings mismatch')) {\n        // purge the state and re-initiate the SSO flow with current settings\n    }\n    throw $e;\n}","preventionTips":["Avoid editing or re-creating SSO settings while users have active SSO flows","Always resolve ssoSettingsId from the current active settings record, not cached/stale copies","Use Validation::uuid() on any settings id before use","Purge sso_states rows after settings rotation or restores"],"tags":["sso","csrf-state","settings-mismatch","uuid","http-400"],"backgroundTag":"invalid-identifier","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"}