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

  1. Inspect the callback request's state parameter — confirm it is present, complete, and not URL-encoded twice.
  2. Restart the SSO flow to obtain a fresh, well-formed state.
  3. Check client code constructing the callback URL that the state query parameter is preserved verbatim (no trimming/re-encoding).
  4. 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

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


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)