passbolt/passbolt_api · error · BadRequestException

The SSO authentication token is invalid. User id mismatch.

Error message

The SSO authentication token is invalid. User id mismatch.

What it means

A BadRequestException from assert() when the token's user_id does not match the requesting user's id from the ExtendedUserAccessControl (or the token's user_id is not a valid UUID). This check guarantees a token can only be used by the user it was issued to.

Solutions

  1. Complete the SSO flow in the same browser session/user context that initiated it
  2. Regenerate the token for the currently authenticated user
  3. Verify the UAC is constructed with the correct user id (ExtendedUserAccessControl built from the right session/user entity)
  4. Log out other sessions and restart the SSO flow if the account was switched mid-flow

Example fix

// before
$uac = new ExtendedUserAccessControl($wrongUserId, $userIp, $userAgent);
$service->assertAndConsume($token, $uac, $settingsId); // user id mismatch
// after
$uac = new ExtendedUserAccessControl($token->user_id, $userIp, $userAgent);
$service->assertAndConsume($token, $uac, $settingsId);
Defensive patterns

Strategy: validation

Validate before calling

if ($token->user_id !== $uac->getId()) {
    throw new \Cake\Http\Exception\BadRequestException('Token belongs to another user');
}

Try / catch

try {
    $service->assertAndConsume($token, $uac, $settingsId);
} catch (\Cake\Http\Exception\BadRequestException $e) {
    if (str_contains($e->getMessage(), 'User id mismatch')) {
        // re-issue a token for the currently authenticated user
    }
}

Prevention

When it happens

Trigger: assert()/assertAndConsume() with a $uac whose getId() differs from $token->user_id — e.g. a different logged-in session completes the SSO callback, or the token was issued for user A while the verify/recover request runs under user B.

Common situations: User switches accounts in another browser tab before completing SSO; session cookie belongs to a different user than the one who initiated SSO; sharing/replaying someone else's verification link; ID confusion between users table UUID and external IdP subject identifier.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at plugins/PassboltEe/Sso/src/Service/SsoAuthenticationTokens/SsoAuthenticationTokenGetService.php:184

     * @throws \Cake\Http\Exception\BadRequestException if the SSO settings is not valid or not matching
     * @return void
     */
    public function assert(SsoAuthenticationToken $token, ExtendedUserAccessControl $uac, string $settingsId): void
    {
        $errorMsg = __('The SSO authentication token is invalid.') . ' ';

        if ($token->isExpired()) {
            throw new BadRequestException($errorMsg . __('The authentication token is expired.'));
        }

        try {
            $sid = $token->getDataProperty(SsoAuthenticationToken::DATA_SSO_SETTING_ID);
        } catch (AuthenticationTokenDataPropertyException $exception) {
            throw new BadRequestException($errorMsg . __('Settings id is missing.'), 400, $exception);
        }

        if ($token->user_id !== $uac->getId() || !Validation::uuid($token->user_id)) {
            throw new BadRequestException($errorMsg . __('User id mismatch.'));
        }

        if (Configure::read('passbolt.security.userIp')) {
            try {
                $ip = $token->getDataProperty(SsoAuthenticationToken::DATA_IP);
            } catch (AuthenticationTokenDataPropertyException $exception) {
                throw new BadRequestException($errorMsg . __('Token IP is missing.'), 400, $exception);
            }

            if ($ip !== $uac->getUserIp()) {
                throw new BadRequestException($errorMsg . __('User IP mismatch.'));
            }
        }

        if (Configure::read('passbolt.security.userAgent')) {
            try {
                $ua = $token->getDataProperty(SsoAuthenticationToken::DATA_USER_AGENT);
            } catch (AuthenticationTokenDataPropertyException $exception) {

View on GitHub (pinned to 31c1bbc10f)