An API authorization test matrix for tenant isolation
An endpoint returning a denial is only one part of an authorization test. The response must not disclose another tenant’s data, and a denied operation must not quietly change a record or start a job.
This guide pairs an explicit access policy with 21 example cases. The application, identities, records, and expected outcomes are fictional. There is no hosted API behind these examples, and none of the requests has been executed.
Get the matrix
The files are ordinary JSON that you can adapt into your team’s test fixtures. The blank file has one incomplete skeleton; populate its policy, fixtures, threat IDs, and assertions before using it. It is not an executable test suite.
Define permission before deciding the expected result
The fictional invoicing service has Tenant Alpha and Tenant Beta. Each has an administrator and a member. Members can list and read all invoices within their own tenant. Administrators can also create, update, delete, and export their tenant’s invoices. Invoice creation does not add an ownership exception. No role grants cross-tenant access.
The server must verify identity, current tenant membership, and permission for the operation. Client-supplied tenant IDs are selectors, not grants. This follows the boundary emphasized in OWASP’s multi-tenant security guidance.
The example’s response contract is:
- 401: Missing or invalid authentication.
- 403: A caller lacks membership in the selected tenant or permission for an operation there.
- 404: An invoice or export is not visible within an otherwise authorized tenant context.
- 200 with an empty collection: A valid scoped search has no matching records.
These are choices for this fictional API. Adapt the matrix to your documented contract. A 404 alone does not establish that access control works.
Read the cases as policy checks
The JSON defines tenant, actor, and resource fixtures. Case references resolve to those fixtures; their UUIDs are synthetic. alphaAdmin, alphaMember, betaAdmin, and betaMember represent active memberships. anonymous has no credential. revokedAlphaMember reuses the Alpha member’s credential after membership removal.
Each case contains prerequisites, request details, expected status, response assertions, side-effect assertions, and a separate execution record. The published execution records all start at not-run, with null observations and no evidence references. In your private adapted copy, record actual observations and evidence before marking a result.
The cases cover:
- AUTHZ-001–007: Allowed reads, foreign invoice reads, and list/search isolation.
- AUTHZ-008–014: Allowed and denied invoice mutations, including a disposable deletion fixture.
- AUTHZ-015–017: Tenant-selector manipulation, missing authentication, and revoked membership.
- AUTHZ-018–021: Export creation, forbidden job creation, foreign export access, and scoped completed output.
Allowed controls matter. A service that rejects every request would satisfy many denial checks while failing its business purpose. OWASP’s authorization regression guidance is a useful reference for organizing checks around actors, resources, actions, and changing access paths.
For allowed lists and completed exports, expected.payload names the required fixture rows, exact item count, and excluded tenants. AUTHZ-005 and AUTHZ-021 require both seeded Alpha invoices, while AUTHZ-007 requires the seeded Beta invoice. An empty result fails these cases. AUTHZ-006 is deliberately different: its search must return zero matches. The completed export fixture names its input invoices so retrieval does not depend on a job from another case finishing.
Denied writes also carry expected.state: invoice fixtures that must remain unchanged and effects that must not occur, such as a queued export job or created export file. Adapt these expectations into actual assertions in your own test runner. The structured fields and instructional strings do not execute checks by themselves.
Try a foreign object inside a permitted tenant context
For AUTHZ-003, authenticate as a Beta member and request an Alpha invoice ID beneath the Beta tenant path:
curl -i \
-H "Authorization: Bearer ${BETA_MEMBER_TOKEN}" \
"https://api.example.test/api/tenants/${BETA_TENANT_ID}/invoices/${ALPHA_INVOICE_ID}"
The expected result is a generic 404 with no Alpha invoice fields or metadata. By contrast, AUTHZ-015 selects Beta’s collection while authenticated only as an Alpha administrator; its expected result is 403 because the selected tenant is unauthorized.
The example host is reserved for illustration. Substitute an authorized test host and private credentials in your own environment. Reset seed data and memberships before each case so earlier changes cannot manufacture a later result.
Check denied writes and background work
AUTHZ-011 uses an Alpha member who can read the invoice but cannot update it:
curl -i -X PATCH \
-H "Authorization: Bearer ${ALPHA_MEMBER_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"amount":125}' \
"https://api.example.test/api/tenants/${ALPHA_TENANT_ID}/invoices/${ALPHA_INVOICE_ID}"
Expect 403. Then use an independently authorized view to verify that the amount and version are unchanged and no mutation job or event was emitted. Do the equivalent for forbidden export creation: no queued job, file, or notification should appear.
For a permitted export, check the worker’s tenant context and the completed file’s rows and metadata. Test retrieval with another tenant’s actor as well. Seed a completed export for retrieval checks; a 202 job response does not mean processing has finished.
Verify data and state, then retain the evidence
Inspect list items, search counts, identifiers, totals, and export metadata for foreign-tenant data. For revocation, issue a credential, remove membership, and retry that same credential. The fictional policy requires denial on the next request; establish and test your own policy explicitly.
Use the threat-model worksheet to explain which boundary each test protects. If a check fails, the vulnerability triage checklist helps record the observed behavior, affected scope, uncertainty, owner, and retest criteria.