{"record":{"id":"d5a33b897bab20f1","repo":"passbolt/passbolt_api","slug":"the-sso-authentication-token-is-invalid-user-id-mismatch","errorCode":null,"errorMessage":"The SSO authentication token is invalid. User id mismatch.","messagePattern":"The SSO authentication token is invalid\\. User id mismatch\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Service/SsoAuthenticationTokens/SsoAuthenticationTokenGetService.php","lineNumber":184,"sourceCode":"     * @throws \\Cake\\Http\\Exception\\BadRequestException if the SSO settings is not valid or not matching\n     * @return void\n     */\n    public function assert(SsoAuthenticationToken $token, ExtendedUserAccessControl $uac, string $settingsId): void\n    {\n        $errorMsg = __('The SSO authentication token is invalid.') . ' ';\n\n        if ($token->isExpired()) {\n            throw new BadRequestException($errorMsg . __('The authentication token is expired.'));\n        }\n\n        try {\n            $sid = $token->getDataProperty(SsoAuthenticationToken::DATA_SSO_SETTING_ID);\n        } catch (AuthenticationTokenDataPropertyException $exception) {\n            throw new BadRequestException($errorMsg . __('Settings id is missing.'), 400, $exception);\n        }\n\n        if ($token->user_id !== $uac->getId() || !Validation::uuid($token->user_id)) {\n            throw new BadRequestException($errorMsg . __('User id mismatch.'));\n        }\n\n        if (Configure::read('passbolt.security.userIp')) {\n            try {\n                $ip = $token->getDataProperty(SsoAuthenticationToken::DATA_IP);\n            } catch (AuthenticationTokenDataPropertyException $exception) {\n                throw new BadRequestException($errorMsg . __('Token IP is missing.'), 400, $exception);\n            }\n\n            if ($ip !== $uac->getUserIp()) {\n                throw new BadRequestException($errorMsg . __('User IP mismatch.'));\n            }\n        }\n\n        if (Configure::read('passbolt.security.userAgent')) {\n            try {\n                $ua = $token->getDataProperty(SsoAuthenticationToken::DATA_USER_AGENT);\n            } catch (AuthenticationTokenDataPropertyException $exception) {","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Service/SsoAuthenticationTokens/SsoAuthenticationTokenGetService.php#L166-L202","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Complete the SSO flow in the same browser session/user context that initiated it","Regenerate the token for the currently authenticated user","Verify the UAC is constructed with the correct user id (ExtendedUserAccessControl built from the right session/user entity)","Log out other sessions and restart the SSO flow if the account was switched mid-flow"],"exampleFix":"// before\n$uac = new ExtendedUserAccessControl($wrongUserId, $userIp, $userAgent);\n$service->assertAndConsume($token, $uac, $settingsId); // user id mismatch\n// after\n$uac = new ExtendedUserAccessControl($token->user_id, $userIp, $userAgent);\n$service->assertAndConsume($token, $uac, $settingsId);","handlingStrategy":"validation","validationCode":"if ($token->user_id !== $uac->getId()) {\n    throw new \\Cake\\Http\\Exception\\BadRequestException('Token belongs to another user');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $service->assertAndConsume($token, $uac, $settingsId);\n} catch (\\Cake\\Http\\Exception\\BadRequestException $e) {\n    if (str_contains($e->getMessage(), 'User id mismatch')) {\n        // re-issue a token for the currently authenticated user\n    }\n}","preventionTips":["Build ExtendedUserAccessControl from the authenticated session user, never raw input","Re-authenticate the user if the session may have switched accounts","Never share SSO verification links between users"],"tags":["sso","authorization","token-validation","user-mismatch"],"backgroundTag":"permission-denied","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}