The problem and decision criteria
As of September 26, 2026
An employee of customer A signs in and opens an order, but sees customer B's order. Login succeeded and the API returned normally. In this design example, the password is not the question. The question is whether the server decided that this employee could perform this action on this order.
Multi-tenant authorization tests must go beyond changing users and roles. Combine user and role × tenant × object × action, and follow the boundary from detail screens through files and background jobs. Successful login or a valid token is insufficient authorization to an object.[1][2]
This article uses an order system shared by customer organizations in one SaaS. Accounts, orders, and policies are synthetic design examples, not actual customers or incidents. Execute verification only in isolated test environments authorized by the owner.
Ask separately about login, role, order, and organization boundaries
Authentication establishes identity. Authorization decides which actions that identity may take on which resources. Here, a tenant is a customer's data and authorization boundary. Sharing infrastructure does not remove the need for explicit cross-organization restrictions.[1][3]
Separate these questions in an order API.
| Dimension | Question | Example if omitted |
|---|---|---|
| Authentication/session | Is the actor trustworthy, and is the session valid? | Handling an unauthenticated request |
| Role/function permission | May this actor delete or export? | A viewer runs administrator export |
| Object permission | May this actor read or modify this order? | Reading another employee's order without permission |
| Tenant boundary | May the actor operate in this organization, and does the target belong to it? | An A administrator deletes B's order |
OWASP API Security Top 10 2023 distinguishes object access failures, BOLA, from unauthorized function execution, BFLA. Permission to use order lookup does not imply access to every order. Unguessable IDs such as UUIDs still require access checks.[2][4]
Another layer remains after allowing the object. A user may read an order but not internal notes, or edit its memo but not tenant_id or owner_id. OWASP's Broken Object Property Level Authorization (BOPLA) addresses field-level read/write restrictions.[5]
Preserve product context as delivery capacity changes — A consistent lead maintains criteria and history while the squad adapts execution and gathers release evidence.
Establish your service's allow rules first
Blocking every other employee's orders is not universally correct. Shared assignees or organization administrators may legitimately require access. Adding denials without allow rules can approve an implementation that blocks normal work.
OWASP ASVS 5.0.0 V8 distinguishes documenting and applying function, data, and field authorization from cross-tenant controls. When turning this into tests, record the relationship that grants access, not only the role name.[6]
Design example: order policies and synthetic data
Tenant A has members U_A1, U_A2, administrator ADM_A, and viewer VIEW_A. Tenant B has member U_B1 and administrator ADM_B. Orders O_A1, O_A2, and O_B1 belong to the corresponding users and tenants. All are synthetic draft orders with “synthetic” in their searchable names. Member, organization administrator, and viewer responses expose only order_id, status, and memo.
Members may read and create their own orders and edit their memos. Organization administrators may read and edit tenant orders, delete drafts, and export. Viewers may read assigned orders only; VIEW_A is assigned only O_A1. Being in the same tenant does not automatically share orders between members.
Support actor SUPPORT_X has no default access. A separate valid approval allows reading only the ID and status of O_A1, with no editing, deletion, or export. Broader support access in a real service requires separately documented approval scope.
Ordinary order updates may not change tenant or owner. Bulk updates use an all-or-nothing denial policy if any object is unauthorized. Restore initial data before checking each case independently.
Fill a user/role × tenant × object × action matrix
These are representative cases derived from the policy, not a complete standard or completed execution results. OWASP guidance recommends role/function/data matrices and multiple test accounts. The examples specialize that approach for orders and customer boundaries.[7][8]
Detail reads and changes
| Case | User/role | Tenant and object | Action | Expected result and evidence |
|---|---|---|---|---|
| M01 | U_A1, member | A, own O_A1 | Read | Allow; return only order_id=O_A1, status, and memo, without other orders |
| M02 | U_A1, member | A, another member's O_A2 | Read | Deny; return no order content or owner information |
| M03 | ADM_A, administrator | A, O_A2 | Read | Allow; do not block legitimate administrator work |
| M04 | U_B1, same member role | A, O_A1 | Read | Deny; identical role does not cross tenant boundaries |
| M05 | ADM_B, administrator | A, O_A1 | Read | Deny; B administrator privileges do not extend to A |
| M06 | VIEW_A, viewer | A, assigned O_A1 | Read | Allow; explicit assignment applies without ownership |
| M07 | VIEW_A, viewer | A, O_A1 | Edit memo | Deny; memo and associated business events remain unchanged |
| M08 | U_A1, member | A, own O_A1 | Edit memo | Allow; only requested memo changes, preserving owner and tenant |
| M09 | U_A1, member | A, another member's O_A2 | Edit memo | Deny; verify stored O_A2 values as well as the response |
| M10 | ADM_A, administrator | B, O_B1 | Delete | Deny; preserve B's order, attachments, and related workflow state |
| M11 | ADM_A, administrator | A, draft O_A2 | Delete | Allow; delete only O_A2, preserving O_A1 and O_B1 |
Lists, bulk operations, creation, and support access
| Case | User/role | Tenant and object | Action | Expected result and evidence |
|---|---|---|---|---|
| M12 | U_A1, member | A; search “synthetic” matches all three orders | List/search | Return only O_A1, with authorized total 1; no O_A2/O_B1 titles or aggregates |
| M13 | ADM_A, administrator | A, O_A1 and O_A2 | Bulk memo edit | Allow; change those two only, preserving O_B1 |
| M14 | ADM_A, administrator | Mixed A/B, O_A1 and O_B1 | Bulk memo edit | Deny all; neither changes under the example's atomic policy |
| M15 | U_A1, member | Select unauthorized B | Create order | Deny; create no row, file, or business event in B |
| M16 | U_A1, member | Authorized A | Create order | Allow; create one order under server-verified A and owner U_A1, none in B |
| M17 | U_A1, member | A, own O_A1 | Change tenant_id or owner_id | Deny; ordinary update cannot transfer tenancy or ownership; original values remain |
| M18 | SUPPORT_X, valid support approval | A, approved O_A1 | Read | Allow only approved ID and status; audit links actual support actor and approval ID |
| M19 | SUPPORT_X, same approval | A, unapproved O_A2 | Read | Deny; approval does not expand to other orders in the tenant |
| M20 | SUPPORT_X, same approval | A, O_A1 | Export | Deny; read approval is not download or export approval |
M14's full denial is an example policy choice. A service supporting partial success must define expectations for processing allowed objects while returning no unauthorized content and producing no unauthorized changes or side effects. Checking only the first object and processing the rest fits neither policy.
Choose 403 or existence-hiding 404 according to API policy. One status code is not the authorization pass criterion. Data in the response or an already changed datastore means denial did not succeed. WSTG bypass testing also examines restricted response data and actual behavior by role.[9]
Client tenant_id selects context; it does not prove permission
A multi-organization user legitimately selects an organization in the UI. The problem is a server trusting the selection without checking it. Tenant IDs in URLs, bodies, or headers identify the selected organization, not the right to act there. Validate context against the authenticated actor's current membership or service permissions.[10]
One language-independent design sequence is:
Validate session/token → check current rights in selected tenant → check action/object relationship → determine allowed fields → read/change → return only permitted results
Object queries should combine object ID with verified tenant and required ownership/assignment conditions. Check memberships separately; do not carry A's administrator role into selected B. Fail if membership cannot be checked or context is missing, rather than broadening queries to all data.
For Next.js App Router, official authentication guidance distinguishes quick Proxy checks from authorization in the data access layer (DAL). Server Actions need public-API security treatment and their own authorization checks. Hiding buttons does not prevent direct calls. Session/role examples still require service-specific order ownership and tenant membership rules.[11]
Recheck with the same session after permissions change
Testing only after logout and login misses stale permissions. Change membership or role after token issuance, then use requests retaining the existing session/token. ASVS 5.0.0 8.3.2 covers reflecting changes to values used for authorization. It also notes that compensating controls for delayed updates, such as self-contained tokens, cannot undo information disclosure.[6]
This example applies new policy from the next authorization check starting after the change is confirmed. In-flight requests and already delivered data are separate. Systems with propagation delays must first decide acceptable delay, risk, and blocking controls, then test that boundary. Do not choose token expiry as an unexplained default grace period.
| Case | Changed condition | Request condition retained | Expected result |
|---|---|---|---|
| T01 | Remove U_A1 from A | Read/edit O_A1 with a token issued before removal | Deny both; a still-active account cannot access A's data |
| T02 | Downgrade ADM_A to viewer assigned only O_A1 | Read/edit/export using the old administrator session | Allow O_A1 read; deny edit, export, and O_A2 read |
| T03 | Remove U_A1 from A and join B | Check the former A token and a normal request establishing valid B context separately | Deny A's O_A1; allow new synthetic B order O_B2 owned by U_A1, deny O_B1; do not migrate orders automatically |
| T04 | Expire or revoke SUPPORT_X approval | Repeat a previously authorized request | Deny even O_A1 reads; test expiry and manual revocation separately |
| T05 | ADM_A loses A access after requesting export | Execute queued user-delegated job | Deny under this example's current-permission recheck policy; create or send no file |
For support access, preserve actual support identity and delegated scope rather than erasing attribution through customer impersonation. Test tenant, target object, allowed action, approval expiry, and revocation together.
Verification that supports a release decision — Verify critical journeys and connect observed results and remaining conditions to the release decision.
Follow the same rules beyond the detail screen
After building the matrix, examine the routes each case traverses. The following route-level test design applies M01–M20 and T01–T05. Declaring a shared API policy does not justify skipping other routes.[1]
| Route | Additional conditions | Results to verify |
|---|---|---|
| Lists/search | Filters, sorting, pagination, suggestions, totals/aggregates | All and only allowed objects; no restricted titles, counts, or pagination metadata |
| Bulk read/edit/delete | Mix nonowned same-tenant and cross-tenant objects | Check each object; storage matches the chosen full-denial or partial-success policy |
| Files/attachments | Previews, originals, derivatives, download-link issuance | Knowing a file ID is insufficient; actual file access matches order access |
| Export | Request, job status, result list, file receipt | Only requester's scope; others cannot retrieve status, results, or file locations using job ID |
| Async jobs | Permission changes while queued, retries, tenant switch in next job | Verified actor, tenant, and scope persist; previous A context does not leak into next B job |
| Cached responses | Cache warmed by an authorized actor, other actors/tenants, revoked permissions | Same allow/deny rules on cache hits |
| Audit and external effects | Denied edit/delete/export | No business changes, payment calls, or sends; retain appropriate denial audit without tokens or file-access secrets |
The asynchronous export here is a job acting for a user. A worker DB account with full access does not authorize the user to export everything. ASVS 5.0.0 8.3.3 requires not substituting an intermediary service's privileges for the original caller's.[6]
Service-authorized jobs such as scheduled settlements or backups independent of user clicks may have different policies. Separately approve the job identity, target tenants, data scope, and recipients. This article proposes keeping the two job types separate instead of one rule granting administrator rights to anything in the background.
Separate cache keys do not remove authorization checks
Authorize requests before reading protected caches. Keys need tenant separation when values or access scope differ by tenant; within a tenant, results can also depend on user, allowed fields, and permission version. Conversely, uniformly public reference data need not be duplicated per user.[10]
Test design example: let U_A1 read its order to warm the cache. Request the same synthetic object as U_A2 and U_B1 to verify isolation. Then remove U_A1 membership and request again while the cache remains warm. Cold-cache-only authorization tests miss this condition.
This checks application authorization. Do not assume one setting also resolves browser/CDN storage permission, TTL, and revalidation policies.
Signed download URLs are a separate access path
An Amazon S3 presigned URL is a bearer-token-like link usable by its holder repeatedly while valid. Validity depends on signing credentials and permissions as well as expiry.[12]
The resulting design implication is clear: deleting application membership in A does not necessarily invalidate existing S3 links. Blocking new link issuance and testing access through previously issued links are separate checks.
If new downloads must stop immediately after revocation, design and verify a download path checking current rights on every request or an effective revocation control. Short expiry alone cannot guarantee immediate blocking. S3 checks expiry at request start, so a download started earlier may continue. Revocation cannot recall already downloaded copies either.[12]
Database RLS reinforces controls but does not solve every route automatically
PostgreSQL 17 row-level security (RLS) restricts rows, but superusers and BYPASSRLS roles bypass it. Table owners generally bypass it too unless configured differently with FORCE ROW LEVEL SECURITY. Read and modification conditions also differ. Test reads, inserts, and updates using the application's actual DB role and policies rather than only an administrator account.[13]
RLS does not automatically establish authorization for copied search results, caches, and files. Nor does this article imply that every non-RLS architecture is vulnerable. First verify that the selected design—consistent application policies, schema/database separation, or other controls—satisfies the required boundary.
Record both responses and actual outcomes
Agree with the environment owner on systems, accounts, time, and permitted actions. Use synthetic data instead of production copies, and route payments, email, and webhooks to test recipients. Take object identifiers from prepared data rather than enumerating or guessing real users' IDs.
Pair each denial with a nearby allowed case. M02 denial needs M01 success alongside it, and M14 bulk denial needs M13 success. Otherwise universal authentication failure can be mistaken for sound authorization. Add unauthenticated and post-logout requests as separate session tests.[9]
Link policy, build and test-data versions, actor and tenant, target object, action, expected result, response, before/after storage differences, and subsequent events. Check that the correct allowed orders are returned, not only that other orders are absent. For M14, changed rows and business events must both be zero under this policy, but legitimate denial audit records need not be absent.
Do not stop with tests replacing authorization itself with a fake allow function. Include integration tests exercising real authentication/authorization and the application's DB role, while controlling external side effects. After a fix, cover normal access and read, update, file, and job paths sharing the policy—not only the failed request.
Record selected cases and exclusion reasons in the Change-based regression scope workbook. This article's matrix specifies expected authorization behavior; the workbook records executed and excluded checks.
Pass means matching authorized scope, not merely being logged in
If your team can prepare policies and synthetic data, begin with existing test tools. A new product is not required to build the matrix. Retain complex delegation, cross-service permission propagation, and immediately revocable file delivery as further design-review topics when boundaries cannot yet be explained.
The proposed release criterion is: exposure or modification of another customer's data cannot be offset by overall test pass rate. Fix the cause and affected paths, then recheck denial and normal access together. Record unverified routes as remaining risk rather than passes. Completing this process is different from guaranteeing the absence of every vulnerability.
For external review of authorization scenarios and scope, see IXC's QA outsourcing service. Agree on accounts, data boundaries, routes, and deliverables before work begins.



