{"record":{"id":"70c4930dda55a4ad","repo":"passbolt/passbolt_api","slug":"the-sso-state-is-invalid-the-sso-state-is-expired","errorCode":null,"errorMessage":"The SSO state is invalid. The SSO state is expired.","messagePattern":"The SSO state is invalid\\. The SSO state is expired\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesAssertService.php","lineNumber":93,"sourceCode":"    /**\n     * Makes assertions against the SSO state entity, current user, and settings ID.\n     * This is used to ensure data integrity between request user/client, settings.\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 assert(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        }\n\n        if ($ssoState->isExpired()) {\n            throw new BadRequestException($errorMsg . __('The SSO state is expired.'));\n        }\n\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","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesAssertService.php#L75-L111","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if ($stateRecord->isExpired()) { /* mint a new state before sending the user to the IdP */ }","typeGuard":null,"tryCatchPattern":"try { $svc->assertAndConsume($state, $settingsId, $uac); } catch (BadRequestException $e) { if (str_contains($e->getMessage(), 'expired')) { // restart flow\n} throw $e; }","preventionTips":["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."],"tags":["php","sso","expired-token","bad-request"],"backgroundTag":"jwt-token-expired","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"}