passbolt/passbolt_api · error · BadRequestException
The SSO state is invalid. Settings mismatch.
Error message
The SSO state is invalid. Settings mismatch.
What it means
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.
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
Example fix
// before: resuming a stale flow with a settings id captured earlier $ssoSettingsId = $state->sso_settings_id; // stale, settings were re-created $service->assertAndConsume($state, $ssoSettingsId, $uac); // after: re-resolve the current settings before asserting $settings = $this->SsoSettings->getActiveSettings(); $service->assertAndConsume($state, $settings->id, $uac); // fresh state must also be created for these settings
Defensive patterns
Strategy: validation
Validate before calling
// validate the settings id before asserting
if (!Validation::uuid($ssoSettingsId) || $ssoState->sso_settings_id !== $ssoSettingsId) {
// abandon state and restart the SSO flow against current settings
} Type guard
// PHP
function isValidSettingsId(string $id): bool
{
return Validation::uuid($id);
} Try / catch
try {
$this->ssoStatesAssertService->assertAndConsume($ssoState, $ssoSettingsId, $uac);
} catch (BadRequestException $e) {
if (str_contains($e->getMessage(), 'Settings mismatch')) {
// purge the state and re-initiate the SSO flow with current settings
}
throw $e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- The SSO state is invalid. User agent mismatch.
- The authentication token should be a valid UUID.
- The comment id is not valid.
- The folder identifier should be a valid UUID.
- The group id is not valid.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/93c5f9aadb52e193.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesAssertService.php:113
if ($ssoState->user_id !== $uac->getId() || !Validation::uuid($ssoState->user_id)) {
throw new BadRequestException($errorMsg . __('User id mismatch.'));
}
if (Configure::read('passbolt.security.userIp')) {
if ($ssoState->ip !== $uac->getUserIp()) {
throw new BadRequestException($errorMsg . __('User IP mismatch.'));
}
}
if (Configure::read('passbolt.security.userAgent')) {
if ($ssoState->user_agent !== $uac->getUserAgent()) {
throw new BadRequestException($errorMsg . __('User agent mismatch.'));
}
}
if ($ssoState->sso_settings_id !== $ssoSettingsId || !Validation::uuid($ssoState->sso_settings_id)) {
throw new BadRequestException($errorMsg . __('Settings mismatch.'));
}
}
/**
* Same assertions but without user ID.
*
* @param \Passbolt\Sso\Model\Entity\SsoState $ssoState SSO state entity.
* @param string $ssoSettingsId SSO Settings ID.
* @param \App\Utility\ExtendedUserAccessControl $uac UAC object.
* @return void
*/
private function assertWithoutUser(SsoState $ssoState, string $ssoSettingsId, ExtendedUserAccessControl $uac): void
{
$errorMsg = __('The SSO state is invalid.') . ' ';
if (!SsoState::isValidState($ssoState->state)) {
throw new BadRequestException(trim($errorMsg));
}View on GitHub (pinned to 31c1bbc10f)