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

  1. Have the user restart the SSO login flow so a fresh state is created against the current settings id
  2. Verify the SSO settings were not edited, deleted, or re-created mid-flow (check sso_settings table created/modified timestamps)
  3. Confirm the ssoSettingsId passed into assertAndConsume/assert is the current settings record's UUID and is valid
  4. Purge stale sso_states rows referencing old/deleted settings ids
  5. 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

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


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)