Testing a Web App with Multiple User Accounts: Privilege Matrices, Session Isolation, and CTEM Role-Based Exposure
Learn how to test a web app with multiple user accounts: build a role and tenant matrix, probe horizontal and vertical privilege escalation, verify session isolation, and keep test data clean.

Testing a Web App with Multiple User Accounts: Privilege Matrices, Session Isolation, and CTEM Role-Based Exposure
TL;DR
Testing a web app with multiple user accounts means building an identity matrix — users, roles, tenants, sessions — and then exercising the same endpoints from every cell to prove the server enforces authorization per object and per function. Horizontal tests catch "user A reads user B's record." Vertical tests catch a low-privilege role invoking a high-privilege action the UI never showed them. Session isolation tests catch cookies, tokens, and caches that leak between identities, and test data hygiene keeps the results trustworthy rather than accidental.
What this is
Multi-account web application testing is a structured method for verifying that an application makes the correct authorization decision for every combination of identity, role, tenant, session, and object — not just for the happy path of a single logged-in user.
Practitioners describe it as a matrix rather than a test case. The axes usually look like this:
- Identity — two or more real accounts, each with its own credentials and its own data.
- Role — end user, manager, support agent, org admin, platform admin, read-only auditor, service account.
- Tenant — the data boundary in a multi-tenant product, whether enforced by
tenant_id, subdomain, or a claim inside a token. - Session — browser cookie jar, mobile token, API key, impersonation session, remember-me token, concurrent login.
- Lifecycle state — invited-but-not-activated, suspended, role-changed-mid-session, deleted-but-token-still-valid.
Three failure classes anchor the work:
- Horizontal privilege escalation — an actor at privilege level N reaches another actor's data at the same level. In API terms this is Broken Object Level Authorization (BOLA).
- Vertical privilege escalation — an actor reaches a function above their level, e.g. a member calling an admin-only endpoint. In APIs this is Broken Function Level Authorization (BFLA).
- Cross-tenant access — the actor is legitimate but the tenant boundary collapses, which is the version most likely to become a breach notification.
Two supporting disciplines make the matrix meaningful. Session isolation asks whether the identity context travels only with the identity that created it — cookie scope, token audience, server-side session keys, cache partitioning, logout and revocation propagation. Test data hygiene asks whether the accounts and records you seeded actually model production boundaries, are isolated from real customer data, and are reproducible on every re-run.
Why it matters
Broken access control has held the top spot in the OWASP Top 10 for a reason: it is the most common high-impact web weakness, and it is structurally invisible to tools that only understand unauthenticated surfaces. Four trends keep it at the top of the agenda.
The UI is no longer the control. Modern applications ship a thin client over a wide API. Hiding an admin button is a UX decision; the enforcement decision lives in a service that must check role and tenant on every call. When teams move fast, endpoints get added without the check, or with a check that trusts a client-supplied role or tenantId field.
Multi-tenancy is the default architecture. SaaS, embedded platforms, and internal platforms-as-a-service all partition data by tenant. A single missing predicate in one query is a cross-customer incident, plus the regulatory clock that comes with it.
Identity changes are continuous. SSO and SCIM provisioning change roles without a re-login. An access token minted before a demotion can stay valid for its full lifetime. Offboarding removes the account but not always the API key, the personal access token, or the refresh token.
Audit and assurance expect evidence. SOC 2, ISO 27001, and PCI DSS all ask how you prove access is restricted to authorized users — repeatedly, across changes, with retained evidence. "We pentested last year" is a thin answer when the authorization code shipped forty times since.
How it works
Horizontal escalation through object references
An authenticated user requests /api/invoices/10432. They change it to 10433 and receive someone else's invoice. Attacks get easier when identifiers are sequential, when list endpoints leak UUIDs the caller does not own, or when GraphQL exposes global node IDs that bypass per-type authorization. GraphQL batching and aliasing let an attacker enumerate hundreds of object IDs in one request, which is why BOLA abuse rarely looks like brute force in the logs.
Vertical escalation through method and route confusion
The UI hides the admin console, but the route still exists. Common patterns:
- Direct request to
/admin/usersor a versioned shadow route (/api/v2/admin/...,/internal/...). - HTTP method swapping —
GET /users/42is protected,DELETE /users/42is not, because the middleware only wrapped the GET handler. - Mass assignment — a
PATCH /meaccepting{"role":"admin"}or{"isAdmin":true}because the model binder maps every field. - Parameter pollution and header trust —
X-User-Role,X-Original-URL, or a duplicated query parameter that the gateway validates but the app reads differently.
Cross-tenant boundary collapse
Tenant context travels in a path segment, a body field, a header, or a token claim. Every place it travels is a place it can be overridden. Subdomain-based tenancy adds a second failure mode: the application reads the tenant from the host header while the authorization check reads it from the session, so a request with a mismatched pair passes one check and fails the other silently. Shared caches make it worse — if a response is keyed only by URL and not by tenant or role, one customer's page can be served to another.
Session isolation failures
Sessions are where identity lives, so they are where leaks happen:
- Cookie scoped to
Domain=.example.com, exposing the session to every subdomain and staging host. - Tokens minted without
aud/issvalidation, so a token from one service is accepted by another. - Server-side session stores with predictable or colliding keys under load.
- Session fixation — the app accepts an attacker-supplied session identifier and elevates it after login.
- Logout that clears the cookie but leaves the server-side session or refresh token alive.
- Role changes and password resets that do not revoke existing sessions.
Test data pitfalls that create false confidence
- A single shared "test user" that belongs to two tenants, so the test proves nothing about the boundary.
- Fixture accounts seeded with production PII, which turns a test environment into a compliance problem.
- Non-deterministic seed data, so a finding cannot be reproduced on the fix team's machine.
- Accounts created through a different path than real users (direct SQL insert instead of the provisioning flow), which skips the very role logic you are testing.
- Leftover accounts after teardown that quietly accumulate privilege over months.
Detection and response
Hypothesis: if the server enforces authorization per object and per function, then a request from user A for user B's object, or from a low-privilege role for an admin-only route, must be denied — and the evidence that confirms or rejects this is a reproducible request/response pair per role showing the expected versus observed decision.
The core question is whether your telemetry can answer "which identity, in which tenant, tried to touch which object, and what did the policy decide?" If the answer requires reading application code, you cannot measure role-based exposure at scale.
What good instrumentation looks like:
- Authorization decision logs with subject ID, effective role, tenant ID, target object ID and type, action, decision, and policy version — emitted server-side, not inferred from WAF rules.
- Correlation IDs that follow a request from gateway to service to data layer, so a 403 in one service can be traced to the upstream call that should have caught it.
- Session and token lifecycle events — issuance, rotation, revocation, role change, password reset, concurrent session creation — so you can detect sessions that outlive the entitlement that justified them.
- Object access graphs — a rolling baseline of which actors normally touch which objects. A user reading an object type they have never touched before, at volume, is a stronger BOLA signal than a raw 403 count.
- Denial-rate telemetry per endpoint, watched for both directions: a spike in 403s (someone probing) and a flatline in 403s on endpoints that should be denied (a broken check).
On the testing side, authenticated scanning matters more than scan volume. A crawler that tests with a single credential set can confirm a missing login wall; it cannot confirm that user A's token is rejected on user B's object. That requires a scanner configured with multiple credential sets, a defined role for each, and — ideally — a target list of object IDs harvested per identity so the comparison is real.
Finally, treat authorization as a regression surface. Every release that touches routing, middleware order, serializers, or policy code is a release that can silently widen access. Continuous re-testing on a schedule tied to deployment cadence is what converts a point-in-time pentest into a measurable control.
What to do
- Build the matrix before you test. Write down every role, every tenant model, and every session type. The matrix is the test plan; without it you are guessing which combinations matter.
- Use one dedicated, isolated account per matrix cell. Separate credentials, separate objects owned by each account, and a documented rule that accounts never share tenants unless cross-tenant access is explicitly under test.
- Test at the object layer, not the interface layer. Capture requests from the UI, then re-issue them with modified identifiers, methods, and bodies. The UI has no opinion about authorization.
- Centralize authorization in a single server-side decision point. Deny by default, resolve tenant from the server-side session or a validated claim — never from a client-supplied field — and check the object's owner against the subject on every read and write.
- Bind tokens to tenant, role, and session, and make them revocable. Validate audience and issuer, keep lifetimes short, and revoke on role change, password reset, and offboarding. A demoted admin with a valid token is still an admin.
- Verify session isolation explicitly. Confirm cookie scope, test logout and revocation propagation across services, check concurrent-session behavior, and confirm the app rotates the session identifier on privilege change.
- Keep test data synthetic and disposable. Generate fake tenants and records, keep production PII out of test environments entirely, seed deterministically, and tear down accounts on a schedule.
- Automate the matrix as regression tests. Encode the highest-value combinations — cross-user object reads, admin-only routes, cross-tenant reads — as tests that run in CI on every release.
- Preserve evidence per finding. A reproducible request/response pair per role, with the expected versus observed decision, is what lets a fix team close the gap instead of debating it.
- Retest after the fix, and then keep retesting. A patch that closes one endpoint frequently leaves the sibling route, the older API version, or the internally reachable service untouched.
FAQ
What is the difference between horizontal and vertical privilege escalation? Horizontal escalation keeps the privilege level constant and crosses a data boundary — user A reaches user B's object, or tenant A reaches tenant B. Vertical escalation crosses a privilege level — a standard user reaches an admin function. Horizontal flaws usually live in object-level checks; vertical flaws usually live in function-level or route-level checks. Mature test matrices cover both, plus the cross-tenant case, which is horizontal escalation with regulatory consequences.
How many test accounts do I actually need? Enough to cover each distinct authorization decision your app makes: one account per role, times at least two identities per role to test horizontal access, times the number of tenancy models you support. A typical multi-tenant SaaS app needs six to twelve accounts — two end users in two tenants, two support or manager roles, one admin, one read-only or auditor role, and one service or API-only identity. Add a suspended account and a mid-lifecycle role-change account if those states exist.
Can a DAST scanner find broken access control on its own? Only partially, and usually only the unauthenticated version. Most scanners test with one session and therefore cannot compare what user A sees against what user B should see. They can catch missing authentication, exposed admin paths, and some insecure direct object references when identifiers are predictable. Confirming a multi-account authorization gap typically needs multiple credential sets, harvested object IDs per identity, and stateful request comparison — which is why role-based exposure is best treated as its own continuous test surface rather than a scanner checkbox.
How does CTEM differ from running scanners? Scanners produce findings; CTEM produces decisions. Continuous Threat Exposure Management combines discovery and inventory with structured scanning, then adds the layer that decides what is actually exploitable and business-relevant, which findings should alarm, and how exposure changes over time as assets and code change. A scanner answers "did this check match?" A CTEM program answers "what should we fix this week, and how do we prove it stayed fixed?"
What test data is safe to use for multi-tenant testing? Synthetic data only. Seed fake tenants, fake users, and fake records that mirror the shape of production — same field lengths, same relationship depth, same identifier formats — without containing production PII. Seed deterministically so findings reproduce, create accounts through the same provisioning path real users take, and tear everything down on a schedule so stale privileged accounts do not accumulate.
Is multi-account testing the same as penetration testing? Multi-account testing is a technique; penetration testing is an engagement that uses it among others. A pentest is scoped and time-boxed, which means the authorization matrix is usually sampled rather than exhausted. Multi-account testing becomes far more valuable when it is automated and run on every release, because authorization code changes constantly and the sample you tested last quarter may no longer reflect the deployed policy.
How often should authorization tests run? At minimum on every release that touches routing, middleware, serializers, policy code, or the data model — and continuously on a schedule for the highest-value combinations: cross-user object reads, admin-only routes, and cross-tenant reads. Pair the automated runs with a manual review of new endpoints at design time, since the cheapest place to catch a missing authorization check is before it ships.
Where this fits in a program
A continuous program starts with attack surface inventory so role-bearing apps and APIs are known, then adds validation so a scanner hit is not treated as proof. API surface testing and deeper web application testing cover the places BOLA and BFLA flaws most often surface. Trusteed ties these into exposure management that decides what deserves attention.
Sources
- Trusteed CTEM — continuous threat exposure management for external and internal attack surface, with deep DAST and API surface testing workers.
- Trusteed CTEM tenant app — operator workflow: assets, scan plans, findings, and validation status.
- Trusteed vulnerability intelligence blog — KEV and emergent-threat narratives that put exploitation context behind prioritization decisions.
- OWASP Web Security Testing Guide — testing procedures for authorization, session management, and identity.
- OWASP API Security Top 10 — BOLA and BFLA definitions and practical mitigation guidance.
- NIST SP 800-53 Rev. 5 — access control and audit logging control families for evidence collection.
- CISA Known Exploited Vulnerabilities Catalog — the KEV context Trusteed uses when ranking what to fix first.