passbolt/passbolt_api · error · BadRequestException

The SSO state is invalid. The SSO state is expired.

Error message

The SSO state is invalid. The SSO state is expired.

What it means

Thrown by SsoStatesAssertService::assert when the SSO state record has passed validity but SsoState::isExpired() returns true — the state token outlived its allowed lifetime. SSO states are short-lived to prevent replay attacks, so an expired state must be rejected.

Solutions

  1. Retry the SSO login from the start — the state must be freshly generated.
  2. Check server clock synchronization (NTP) to avoid premature expiry.
  3. If states expire too quickly for your users, review the SsoState expiry duration in the plugin configuration.
Defensive patterns

Strategy: try-catch

Validate before calling

if ($stateRecord->isExpired()) { /* mint a new state before sending the user to the IdP */ }

Try / catch

try { $svc->assertAndConsume($state, $settingsId, $uac); } catch (BadRequestException $e) { if (str_contains($e->getMessage(), 'expired')) { // restart flow
} throw $e; }

Prevention

When it happens

Trigger: User waits too long between starting SSO login and returning from the IdP (state TTL exceeded); page left open and completed later; server clock skew making states expire early.

Common situations: User gets distracted mid-login and submits the IdP callback minutes/hours later; misconfigured server timezone/clock causing premature expiry; retrying an old bookmarked callback URL.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/70c4930dda55a4ad. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesAssertService.php:93

    /**
     * Makes assertions against the SSO state entity, current user, and settings ID.
     * This is used to ensure data integrity between request user/client, settings.
     *
     * @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 assert(SsoState $ssoState, string $ssoSettingsId, ExtendedUserAccessControl $uac): void
    {
        $errorMsg = __('The SSO state is invalid.') . ' ';

        if (!SsoState::isValidState($ssoState->state)) {
            throw new BadRequestException(trim($errorMsg));
        }

        if ($ssoState->isExpired()) {
            throw new BadRequestException($errorMsg . __('The SSO state is expired.'));
        }

        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.'));
            }
        }

View on GitHub (pinned to 31c1bbc10f)