passbolt/passbolt_api · error · BadRequestException
The SSO state is invalid.
Error message
The SSO state is invalid.
What it means
Before hitting the database, SsoStatesGetService::getOrFail validates the format of the incoming state token with SsoState::isValidState(). A malformed, empty, or otherwise invalid state string yields this BadRequestException (HTTP 400) without any DB lookup. The state must match the format generated when the SSO flow was initiated.
Solutions
- Inspect the callback request's state parameter — confirm it is present, complete, and not URL-encoded twice.
- Restart the SSO flow to obtain a fresh, well-formed state.
- Check client code constructing the callback URL that the state query parameter is preserved verbatim (no trimming/re-encoding).
- Look at SsoState::isValidState() to see the accepted format and compare with what your client sends.
Example fix
// before: state lost when building redirect URL
return $this->redirect("/sso/verify?provider=$provider");
// after
return $this->redirect('/sso/verify?' . http_build_query(['provider' => $provider, 'state' => $state])); Defensive patterns
Strategy: validation
Validate before calling
// Client-side pre-check before invoking the callback endpoint
if (!is_string($state) || $state === '' || strlen($state) < 16) {
// abort: state missing/malformed, restart SSO flow
} Type guard
function isWellFormedState(mixed $state): bool {
return is_string($state) && preg_match('/^[A-Za-z0-9]+$/', $state) === 1 && $state !== '';
} Try / catch
try {
$ssoState = $getService->getOrFail($state);
} catch (BadRequestException $e) {
// state string failed isValidState(): restart flow, do not retry same token
} Prevention
- Pass the state parameter through redirects unmodified (http_build_query, no manual concatenation).
- Avoid double URL-encoding in redirect chains.
- Guard client code against sending undefined/null state.
- Familiarize with SsoState::isValidState() to know the accepted format.
When it happens
Trigger: Calling getOrFail with a state string that fails SsoState::isValidState() — e.g. missing, empty, truncated, URL-encoded incorrectly, or tampered state parameter on the SSO callback/verify endpoint.
Common situations: The browser extension or client sends the callback without the state query parameter; the state gets truncated by an intermediary or copy-paste error; a security scanner or CSRF probe hits the endpoint with garbage state values; the state was double-encoded/decoded in a redirect chain.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Could not save the SSO state, invalid nonce.
- The SSO setting id should be a uuid.
- The SSO state type is invalid.
- Account recovery case must be a string.
- Ajax/Json request not supported.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/72a36c352dda5449.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/Sso/src/Service/SsoStates/SsoStatesGetService.php:37
use Cake\Datasource\Exception\RecordNotFoundException;
use Cake\Http\Exception\BadRequestException;
use Cake\ORM\Locator\LocatorAwareTrait;
use Passbolt\Sso\Model\Entity\SsoState;
class SsoStatesGetService
{
use LocatorAwareTrait;
/**
* @param string $state State to find.
* @return \Passbolt\Sso\Model\Entity\SsoState
* @throws \Cake\Datasource\Exception\RecordNotFoundException When given state doesn't exist or not active.
* @throws \Cake\Http\Exception\BadRequestException If given SSO state is invalid.
*/
public function getOrFail(string $state): SsoState
{
if (!SsoState::isValidState($state)) {
throw new BadRequestException(__('The SSO state is invalid.'));
}
/** @var \Passbolt\Sso\Model\Table\SsoStatesTable $ssoStatesTable */
$ssoStatesTable = $this->fetchTable('Passbolt/Sso.SsoStates');
try {
/** @var \Passbolt\Sso\Model\Entity\SsoState $ssoState */
$ssoState = $ssoStatesTable
->find('active')
->where(['state' => $state])
->firstOrFail();
} catch (RecordNotFoundException $e) {
throw new RecordNotFoundException(__('The SSO state does not exist.'), 400, $e);
}
return $ssoState;
}
}View on GitHub (pinned to 31c1bbc10f)