passbolt/passbolt_api · error · BadRequestException

The user is missing for the SSO state.

Error message

The user is missing for the SSO state.

What it means

assertStateCodeAndGetUac() converts an OAuth state into a UAC, but the state must carry a user_id. When the SSO state entity has a null user_id (state created without a bound user), a BadRequestException is thrown because the flow cannot identify which user is logging in.

Solutions

  1. Ensure the flow creates the SSO state with a user_id (associate the state with the user before redirecting to the IdP)
  2. If the flow intentionally has no bound user (e.g. first-time SSO setup), use the endpoint designed for user-less states instead of assertStateCodeAndGetUac
  3. Start the SSO login flow again to generate a fresh state with the user bound

Example fix

// before
$state = $this->SsoStates->create($settingsId, null); // no user
// after
$state = $this->SsoStates->create($settingsId, $uac->getId()); // bind user id
Defensive patterns

Strategy: validation

Validate before calling

if ($ssoState->user_id === null) {
    // do not call assertStateCodeAndGetUac; use user-less flow endpoint or re-create state
}

Type guard

function stateHasUser(object $state): bool { return $state->user_id !== null; }

Try / catch

try {
    $uac = $service->assertStateCodeAndGetUac($ssoState, $code, $ip, $ua);
} catch (BadRequestException $e) {
    if ($e->getMessage() === 'The user is missing for the SSO state.') {
        // restart flow with a user-bound state
    }
}

Prevention

When it happens

Trigger: Recovering/completing an SSO login whose state was generated without a user (e.g. anonymous SSO-recover setup flows, or states created before user association), then hitting the assert/login endpoint with that state code.

Common situations: SSO recover/setup flows where the user id is only attached later in the flow; replaying an old state; race where state was created by a different endpoint variant; bugs in plugin code creating states without user binding.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at plugins/PassboltEe/Sso/src/Service/Sso/AbstractSsoService.php:190

     * Check a given state against authentication token and extended user info
     *
     * @param \Passbolt\Sso\Model\Entity\SsoState $ssoState SSO state entity
     * @param string $code client ip
     * @param string $ip user agent
     * @param string $userAgent user agent
     * @throws \Cake\Http\Exception\BadRequestException If the user_id in SSO state is `null`.
     * @throws \Cake\Http\Exception\BadRequestException if the user does not exist or is inactive
     * @throws \Cake\Http\Exception\BadRequestException if resource owner username is not provider or does not match user entity
     * @return \App\Utility\ExtendedUserAccessControl
     */
    public function assertStateCodeAndGetUac(
        SsoState $ssoState,
        string $code,
        string $ip,
        string $userAgent
    ): ExtendedUserAccessControl {
        if ($ssoState->user_id === null) {
            throw new BadRequestException(__('The user is missing for the SSO state.'));
        }

        try {
            $user = (new UserGetService())->getActiveNotDeletedNotDisabledOrFail($ssoState->user_id);
        } catch (NotFoundException $exception) {
            throw new BadRequestException(__('The user does not exist or is not active.'), 400, $exception);
        }

        // Check the token against extended user info and consume it
        $uac = new ExtendedUserAccessControl($user->role->name, $user->id, $user->username, $ip, $userAgent);
        (new SsoStatesAssertService())->assertAndConsume($ssoState, $this->getSettings()->id, $uac);

        try {
            // Assert access request and if it matches current suer
            $resourceOwner = $this->getResourceOwnerAndAssertAgainstUser($code, $user);

            $this->assertResourceOwnerAgainstSsoState($resourceOwner, $ssoState);
        } catch (Exception $e) {

View on GitHub (pinned to 31c1bbc10f)