passbolt/passbolt_api · warning · BadRequestException

The SSO authentication token is invalid. User IP mismatch.

Error message

The SSO authentication token is invalid. User IP mismatch.

What it means

A BadRequestException from assert() raised when 'passbolt.security.userIp' is enabled and the IP stored in the token's data differs from the client IP in the ExtendedUserAccessControl. This IP pinning prevents a token from being replayed from a different network location.

Solutions

  1. Complete the SSO flow from the same network/IP used to initiate it
  2. If behind proxies/load balancers, ensure X-Forwarded-For / client IP detection is consistent (Configure proxy detector in App config)
  3. Set passbolt.security.userIp = false in config/passbolt.php if IP pinning is unsuitable for your environment, then restart the flow for a new token
  4. Generate a fresh token after the network change

Example fix

// before
// passbolt.php missing proxy trusted proxies config -> wrong detected IP
// after
'App' => [
    'fullBaseUrl' => 'https://passbolt.example.com',
],
// ensure trusted proxies are configured so getUserIp() matches the IP stored at token creation
Defensive patterns

Strategy: try-catch

Validate before calling

$ipMatches = !\Cake\Core\Configure::read('passbolt.security.userIp')
    || $token->getDataProperty(\Passbolt\Sso\Model\Entity\SsoAuthenticationToken::DATA_IP) === $uac->getUserIp();

Try / catch

try {
    $service->assertAndConsume($token, $uac, $settingsId);
} catch (\Cake\Http\Exception\BadRequestException $e) {
    if (str_contains($e->getMessage(), 'User IP mismatch')) {
        // ask the user to restart SSO from a stable network
    }
}

Prevention

When it happens

Trigger: assert()/assertAndConsume() where $ip (token DATA_IP) !== $uac->getUserIp(). Happens when the user's IP changed between starting the SSO flow and completing it — VPN connect/disconnect, mobile network switch, load-balanced requests hitting different egress IPs, or proxy header misconfiguration.

Common situations: Corporate VPN or NAT pools rotating IPs; requests behind different reverse proxies where X-Forwarded-For isn't consistently forwarded; IPv4 vs IPv6 mismatch (client switches between them); disabling the check via passbolt.security.userIp=false for such environments.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

        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) {
                throw new BadRequestException($errorMsg . __('User agent is missing.'), 400, $exception);
            }
            if ($ua !== $uac->getUserAgent()) {
                throw new BadRequestException($errorMsg . __('User agent mismatch.'));
            }
        }

        if ($sid !== $settingsId || !Validation::uuid($sid)) {
            throw new BadRequestException($errorMsg . __('Settings mismatch.'));
        }
    }

View on GitHub (pinned to 31c1bbc10f)