Access control is the control that fails most often and the one automated tooling is worst at checking. The reason is simple: a broken access control request is not malformed, so there is no bad pattern to detect. Deciding it is a bug requires knowing who was supposed to be allowed. This guide covers how to test access control in a web application or API, in an order you can actually work through, and what a correct result looks like at each step.
Before you start: build the matrix
Access control testing is comparison testing, so the first job is having things to compare. You need accounts and you need an inventory.
- At least two accounts at the same level, so you can test one member against another. A single account cannot reveal a horizontal bug.
- One account per privilege level the product has: member, administrator, billing owner, support, and anything internal.
- Two tenants if the product is multi tenant. Two workspaces owned by different people is the only way to test the boundary that matters most to your customers.
- A list of objects and who owns them. Note the ids created by each account. You will use this constantly.
- A capture of normal traffic per level. Drive the application as each account with a proxy or the browser network tab recording. That capture is your test suite.
Write down, in one table, which roles are meant to be able to do what. Most teams have never written this down, and the act of writing it usually surfaces two or three rules nobody had agreed on.
How to test access control, step by step
1. Swap object ids between accounts
Signed in as user A, request the objects owned by user B. Change the id in the path, the query string, the JSON body, and any header that carries one.
GET /api/notes/4121 Authorization: Bearer tokenForUserA expected: 403 Forbidden or 404 Not Found finding: 200 OK with user B's data
Repeat per verb, because read and write are authorized separately. A GET that is correctly denied says nothing about the PATCH or DELETE on the same object.
2. Replay privileged requests with an unprivileged token
Take the capture from the admin account and send those exact requests with a member token. This is the fastest test in the whole list and it finds the most serious bugs, because a route that only checks that you are signed in will answer anyone who is.
3. Try to write fields you should not control
Add attributes to bodies that do not document them, then read the record back to see whether the value stuck.
PATCH /api/users/me
{ "display_name": "Sam", "role": "admin", "workspace_id": 9 }
The usual candidates are role, is_admin, plan, scopes, owner_id, tenant_id, and verified. A response that echoes your value back is not proof on its own. Fetch the object again as a different account to confirm the change persisted.
4. Follow every object into its quiet paths
The direct fetch is the route people remember to protect. The same record is usually reachable through several others.
- List and search endpoints, which often filter in the interface rather than the query
- Export and report jobs, which run in the background with service credentials
- File downloads and signed links, where the link may outlive the permission
- Notification emails and webhooks, which quote object contents to whoever is subscribed
- Older API versions still routed and no longer maintained
5. Test permission over time, not just at one moment
Access control has a lifecycle, and testing at a single point misses the whole class of stale authority.
- Demote an account, then reuse the token it was issued before the change
- Remove a member from a workspace, then replay their earlier requests
- Cancel an invitation, then accept it
- Delete an object, then request it directly by id, and check restore and undelete paths
6. Check the tenant boundary explicitly
With an account in workspace A, try to read, write, and invite into workspace B. Then do it through the quiet paths from step 4. Cross tenant leakage is the finding that ends enterprise deals, and it is frequently absent from an application’s test suite entirely.
A correct response is a denial you can prove, not the absence of a link in the interface. If the button is hidden but the endpoint answers, the control does not exist.
What a correct result looks like
- 403 or 404 on every unauthorized request. Prefer 404 for objects the caller should not know about, since a 403 confirms the record exists.
- Denials that come from the server, reproducible outside the browser with a raw request.
- Consistent answers across paths. If the direct fetch denies and the export includes the record, the control is not enforced, it is decorated.
- A test per object route asserting that user A cannot reach user B’s object. Without these, the next refactor quietly reopens what you just fixed.
Where automation helps and where it does not
Scanners are good at the parts with a signature: missing authentication on a route, a directory that lists, a known vulnerable component. They struggle here because every request in this guide is legal. The tool has no way to know that note 4121 belongs to someone else, or that only the billing owner should change a plan. That knowledge is specific to your application and it does not exist anywhere in the code in a checkable form. More on where scanners stop and research starts is here.
What can be automated is the part that is mechanical once the intent is understood: holding several identities at once, enumerating every object and route, and replaying each request as each account. That is a lot of combinations and exactly the sort of work people skip when a release is due. It is also the direction we are building in. As an early signal, a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. We are still early and it needs supervision, but the shape of the work suits a system that can reason about what an application is for rather than what its inputs look like.
If you do only one thing from this guide, do step 2. Record what an administrator does, replay it as an ordinary member, and see what answers. It takes an afternoon and it finds the bugs that matter. Read more about how UnboundCompute works.
Frequently asked questions
How do you test access control in a web application?
Create at least two accounts at the same level plus one per privilege level, record the objects each one owns, and capture the normal traffic of each. Then run six checks: swap object ids between accounts, replay admin requests with a member token, try to write fields such as role that you should not control, follow each object into list, export and download paths, retest after permissions change, and probe the tenant boundary. A correct application answers 403 Forbidden or 404 Not Found every time.
What is the fastest access control test to run?
Record everything an administrator account does, then send those exact requests using an ordinary member token. It takes an afternoon and it finds the highest severity issues, because any route that checks only that you are signed in will answer whoever asks. Hiding a button removes the path a normal user takes to an endpoint, not the endpoint itself.
Why can scanners not find access control bugs?
Because the requests are legal. Every field has the right type, the session is valid, and the response is a clean 200 OK. A scanner works by comparing traffic against known bad patterns, and there is no pattern here to match. Knowing that a particular record belongs to a different user, or that only a billing owner may change a plan, is knowledge about your specific application that does not exist in the code in any checkable form.
How many test accounts do I need for access control testing?
At least four in most products. Two accounts at the same level, because a single account cannot reveal a horizontal bug where one member reads another member’s data. One account per elevated level, such as administrator or billing owner. And if the product is multi tenant, a second workspace owned by an unrelated person, since cross tenant access is the failure customers care about most and the one least often covered by an existing test suite.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.


