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
- Retry the SSO login from the start — the state must be freshly generated.
- Check server clock synchronization (NTP) to avoid premature expiry.
- 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
- Complete the IdP round-trip promptly; don't leave login tabs open.
- Sync server clocks via NTP.
- Re-generate state on every login attempt instead of retrying stale ones.
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
- Service provider invalid.
- Service provider missing.
- Service provider not supported.
- The authentication token has been expired.
- The SSO state is invalid.
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)