Back to guides

GUIDE / security

How to Test for Broken Access Control

Learn how to test for broken access control: IDOR, RBAC, vertical and horizontal privilege checks, API object access, and a practical BAC checklist.

By The Testing AcademyPublished July 9, 2026Updated July 9, 202617 min read

Hidden buttons are not access control. How to test for broken access control focuses on identity, ownership, and verbs: can user A read user B's objects, can a standard role hit admin APIs, and does every valuable action enforce server-side rules for every actor.

This guide teaches a practical method to test for broken access control: horizontal and vertical checks, IDOR/BOLA probes, RBAC matrices, forced browsing, API-first testing, multi-tenant traps, common mistakes, and a release checklist. You will leave with test cases you can run in staging this week.

For related depth, read authentication and authorization testing, OWASP Top 10 for testers, the API security testing checklist, and security testing for QA.

Ethics and Safety

  • Use authorized environments and test accounts you control.
  • Prefer staging with synthetic personal data.
  • Do not exfiltrate real customer content to prove impact.
  • Stop at proof of access; do not bulk-download data.
  • Coordinate if tests might trigger fraud or abuse systems.

What Broken Access Control Means

Access control answers:

  1. Who are you? (authentication)
  2. What are you allowed to do? (authorization)

Broken access control is primarily a failure of question two, often after question one succeeded. You have a valid login, but the server does not correctly limit your actions.

Common forms:

  • Horizontal privilege escalation: peer user access.
  • Vertical privilege escalation: lower role performs higher role actions.
  • Context/tenant failures: correct role, wrong organization.
  • Function-level failures: can invoke disallowed operations.
  • Field-level failures: can modify protected attributes (role, balance).
  • Unauthenticated access: protected resources available with no login.

Why UI Testing Is Not Enough

The UI may hide "Delete user" from non-admins. Attackers call:

DELETE /api/admin/users/55

with a normal user token. If the server trusts the client, the control is broken.

Rule: every authorization rule must be enforced server-side and tested without relying on the UI.

How to Test for Broken Access Control: Build the Authorization Model First

Before changing IDs at random, document intended rules.

Identify actors

Examples:

  • Anonymous visitor
  • Authenticated user
  • Org member
  • Org admin
  • Support agent
  • Superadmin
  • Partner API client

Identify resources

Examples:

  • Profile
  • Order / invoice
  • Document / file
  • Project
  • API key
  • Webhook config
  • Admin report

Identify actions

  • List
  • Read
  • Create
  • Update
  • Delete
  • Export
  • Approve
  • Share
  • Transfer ownership

Build a matrix

Action on InvoiceAnonUser (owner)User (other)Org adminSuperadmin
ListDenyOwnDeny othersOrg scopeAll / policy
ReadDenyOwnDenyOrg scopeAllow
RefundDenyDenyDenyLimitedAllow
Export PDFDenyOwnDenyOrg scopeAllow

Each cell is a potential test case. This matrix is the heart of learning how to test for broken access control systematically.

Horizontal Testing: Peer Access and IDOR

Horizontal tests ask: can User B access User A's object?

Step-by-step IDOR method

  1. Log in as User A.
  2. Create or locate a resource owned by A. Note its ID.
  3. Log in as User B (same role, different owner).
  4. Attempt read, update, delete, share, and export on A's ID.
  5. Attempt list/search filters that might return A's object.
  6. Record status codes and whether data or side effects occurred.

Example targets:

GET    /api/orders/1001
PUT    /api/orders/1001
DELETE /api/orders/1001
GET    /api/orders/1001/invoice.pdf
GET    /api/orders?userId=A
GET    /api/orders?search=A-PO-number

ID patterns to try

  • Sequential integers: 1001, 1002, 1000
  • UUIDs captured from other accounts (never assume UUID equals authorization)
  • Emails or usernames in paths
  • Nested IDs: /projects/12/tasks/99
  • Secondary keys: account numbers, invoice numbers, short codes

Expected secure outcomes

  • 403 Forbidden or 404 Not Found per product design
  • No sensitive body leakage on deny
  • No state change on unauthorized write
  • Consistent behavior across GET and mutation verbs

404 can be intentional to avoid existence leaks. 403 is also fine. 200 with someone else's data is a defect. 500 with stack traces is a separate quality and security issue.

Vertical Testing: Role Climbing

Vertical tests ask: can a lower privilege role perform higher privilege functions?

Method

  1. Capture admin-only requests while logged in as admin (or from API docs).
  2. Replay those requests with a standard user token.
  3. Replay with support role if that role is semi-privileged.
  4. Confirm denials for GET, POST, PUT, PATCH, DELETE, not only one verb.

Examples:

GET  /api/admin/users
POST /api/admin/users/55/disable
PUT  /api/admin/feature-flags
GET  /internal/reports/revenue

Also test:

  • Hidden front-end routes (/admin, /settings/billing/advanced)
  • Feature-flagged panels that still call live APIs
  • "Impersonate user" tools restricted to support

Function-Level Authorization

Sometimes a user can open an object but should not perform certain actions.

Examples:

  • Can view order, cannot refund.
  • Can comment, cannot delete thread.
  • Can read employee directory, cannot export all emails.
  • Can create tickets, cannot assign severity beyond a limit.

Test by mapping buttons to API calls, then calling disallowed functions directly.

Multi-Tenant and Organization Boundaries

SaaS products often fail here.

Fixtures:

  • Org1 Admin, Org1 Member
  • Org2 Admin, Org2 Member

Probes:

  1. Org2 member reads Org1 object by ID.
  2. Org2 admin lists with filter orgId=Org1.
  3. Invite flows that attach users to the wrong tenant.
  4. Switching tenant in UI while replaying old tokens.
  5. Shared resources and cross-tenant sharing rules.

Tenant ID in a JWT claim is not enough if handlers ignore it or trust client-supplied orgId in the body.

Field-Level and Mass Assignment Access Control

Broken access control includes writing fields you should not control.

Probe:

PATCH /api/users/me
{
  "displayName": "B",
  "role": "admin",
  "accountBalance": 99999,
  "emailVerified": true
}

Expected: protected fields ignored or rejected.

Also test create endpoints:

POST /api/projects
{
  "name": "x",
  "ownerId": "someone-else",
  "plan": "enterprise"
}

Forced Browsing and Discovery

Find targets beyond the happy path:

  • Sitemap, mobile app traffic, old admin bookmarks
  • OpenAPI/Swagger docs in staging
  • JavaScript bundles referencing admin routes
  • robots.txt and public docs
  • Parameter names in one endpoint reused in others

Then attempt access as lower privilege users.

API-First Testing Beats Click-Only Testing

Recommended workflow:

  1. Establish sessions/tokens for each actor.
  2. Build a collection of resource endpoints.
  3. Parameterize IDs and tokens.
  4. Run cross-actor request grids.
  5. Validate response bodies, not only status codes.
  6. Confirm side effects with a second read as the owner.

Browser testing still matters for:

  • Client-side route guards that confuse teams (document that they are not security)
  • Download flows and file viewers
  • Multi-step UI workflows that produce complex payloads

But the authorization truth is on the server.

GraphQL and Batch Endpoint Notes

GraphQL can hide many operations behind one HTTP route.

Test:

  • Unauthorized queries and mutations by name
  • Object IDs in nested selections
  • Batching that returns other users' nodes
  • Introspection exposure policies

Example idea:

query {
  invoice(id: "INV_OF_OTHER_USER") {
    id
    amount
    customerEmail
  }
}

If this returns data for an unauthorized user, you found BAC.

Access control often breaks on secondary delivery paths:

  • Direct file URLs
  • Cloud storage signed URLs with long expiry and guessable paths
  • CSV exports
  • PDF generators
  • Share links that never expire and are not revocable
  • "Anyone with link" vs restricted visibility mismatches

Test:

  1. Owner creates file.
  2. Other user tries direct object URL.
  3. Expired share link denied.
  4. Revoked share link denied.
  5. Export includes only authorized rows.

Creating a Practical BAC Test Pack

Minimum actors

  • UserA
  • UserB
  • Admin (or OrgAdmin)

Minimum resources

Pick your top three valuable objects (for example invoice, document, project).

Minimum actions per object

Read, update, delete, export/list.

Core grid

#ActorTargetActionExpected
1UserBUserA objectReadDeny
2UserBUserA objectUpdateDeny
3UserBUserA objectDeleteDeny
4UserAUserA objectReadAllow
5UserAdmin functionAnyDeny
6AnonProtected objectReadDeny
7Org2Org1 objectReadDeny
8UserProtected field writePatch roleDeny

Expand from this skeleton until critical resources are covered.

Example Test Cases

IDTitlePreconditionsStepsExpectedPriority
BAC-01Cross-user read deniedOrders for A and BAs B, GET A's orderDeny, no body leakCritical
BAC-02Cross-user update deniedSameAs B, PUT A's order noteDeny, data unchangedCritical
BAC-03Admin API denied to userCapture admin list callReplay as userDenyCritical
BAC-04Nested resource checkProject A with task 99As B, open task 99 URLDenyHigh
BAC-05Export scopeMany orgsAs org admin, exportOnly own org rowsCritical
BAC-06Mass assignment roleUser tokenPATCH role=adminRole unchangedCritical
BAC-07Unauth accessNoneGET protected without token401High
BAC-08Share link revokeShared docRevoke, open link as otherDenyHigh
BAC-09Search leakDistinct order numbersAs B, search A's order numberNo A dataHigh
BAC-10Verb confusionUser can GET ownAs B, POST action on A's idDenyHigh

Status Code and Response Hygiene

While testing access control, also observe:

  • Unauthorized responses that include stack traces
  • Deny responses that still include object titles or emails
  • Different timing that reveals object existence if that matters for your threat model
  • Verbose "access denied for user X to invoice Y owned by Z" messages that leak ownership maps

Perfect existence hiding is not always required, but accidental data leakage in deny paths is still a defect.

How to Prove Impact Responsibly

Good proof:

  • Show one unauthorized object read with two user IDs.
  • Show one unauthorized state change and revert it.
  • Redact personal fields in tickets if needed.

Bad proof:

  • Dumping hundreds of records.
  • Accessing real production customer data without authorization.
  • Changing production admin passwords.

Filing a Strong Broken Access Control Report

Title: IDOR allows any authenticated user to read other users' invoices
Actors:
- userA@example.test owns invoice inv_1001
- userB@example.test is same role, different account
Request:
GET /api/v1/invoices/inv_1001
Authorization: Bearer <userB token>
Actual: 200 OK with line items, amount, billing email of userA
Expected: 404 or 403 with no invoice payload
Impact: Horizontal broken access control over billing data; privacy and trust risk; possible compliance impact
Scope notes: Also verified PUT is denied; issue appears read-path only
Remediation direction: enforce ownership/tenant checks in invoice read handler and shared policy layer; add regression tests for cross-user read
Evidence: sanitized screenshots and response JSON with PII masked

Common Mistakes When Testing Access Control

Mistake 1: Testing only with one user

Without UserA/UserB pairs, IDOR remains invisible.

Mistake 2: Trusting hidden buttons

UI is not a security boundary.

Mistake 3: Only testing GET

Writes and deletes are often more damaging.

Mistake 4: Only testing sequential IDs

UUID systems still need cross-user tests using captured IDs.

One search endpoint can leak many objects.

Mistake 6: Ignoring nested resources

Parent authorization does not always flow correctly to children.

Mistake 7: No multi-tenant fixtures

Many serious SaaS bugs are tenant confusions, not role confusions.

Mistake 8: Closing bugs without regression tests

BAC issues return when new handlers skip the policy layer.

Automation Strategy

Automate stable denials:

  1. Cross-user read on one representative resource type.
  2. User token against one admin endpoint.
  3. Unauthenticated access to one protected endpoint.
  4. Mass assignment attempt on profile.

Keep exploratory matrices for new modules. Pure automation without new-object coverage still drifts.

Broken Access Control Checklist

Actors and setup
[ ] At least two users same role
[ ] At least one higher privilege role
[ ] Second tenant/org if multi-tenant
[ ] Tokens/sessions documented

Horizontal
[ ] Cross-user read/update/delete tested on top resources
[ ] Nested IDs tested
[ ] List/search/export tested for leakage
[ ] Files and share links tested

Vertical
[ ] Admin/support endpoints replayed as lower roles
[ ] Forced browsing to privileged routes
[ ] Function-level actions denied correctly

Data shaping
[ ] Protected fields cannot be written
[ ] Client-supplied owner/org IDs cannot reassign access

Hygiene
[ ] Deny responses do not leak sensitive payloads
[ ] 401/403/404 behavior consistent with design
[ ] Issues filed with two-actor evidence
[ ] Regression cases added after fixes

How BAC Relates to Authentication Bugs

Sometimes a failure looks like access control but is authentication:

  • Token not required at all
  • Token from deleted user still works indefinitely
  • Session fixation

Test both layers. Authn proves identity. Authz limits action. Guides like how to test JWT authentication and API authentication testing cover the identity side.

Team Workflow That Prevents BAC Recurrence

  1. Design: write access rules in the story.
  2. Implement: central policy helpers, not copy-paste checks.
  3. QA: matrix tests before release.
  4. Automate: critical denials in CI.
  5. Pentest/AppSec: periodic deep review.
  6. Incident learning: each escape becomes a regression fixture.

Broken access control is as much a process problem as a technical one.

Practice

Access control testing rewards discipline more than cleverness. Practice building actor matrices and two-user proofs. For structured QA skill practice, create an account at QABattle sign-up and train scenario discipline in battles.

How to Test for Broken Access Control on Real Sprints

Insert BAC work into ordinary delivery with minimal ceremony.

During story refinement

Ask:

  • Who can do this action today?
  • Who must never do this action?
  • Is this object tenant-scoped, user-scoped, or global?
  • Are there secondary delivery paths (export, email link, API, mobile)?

Write at least one denial acceptance criterion for sensitive stories.

During test design

Add matrix rows before scripted UI cases. Authorization denials are not optional extras.

During execution

Run cross-user API probes on the same day you run happy path UI tests. Do not defer all security to the end.

During regression

Keep a living pack of the top denied actions. Every fixed BAC bug adds a case.

Advanced Nested Authorization Examples

Example: project files

/projects/{projectId}/files/{fileId}

Tests:

  1. User in project can read file.
  2. User outside project cannot read file even with valid fileId.
  3. User removed from project loses file access on next request.
  4. Public share link rules override correctly and revocably.
  5. File IDs from project A do not work under project B paths if the server keys only on fileId.

Example: support tickets with internal notes

GET /tickets/88
GET /tickets/88/internal-notes

Customer user may read ticket but never internal notes. Support agent may read both. Test both endpoints, not only the parent.

Caching and CDN Access Control Pitfalls

Sometimes the app authorizes correctly on first request, then a cache serves content to the wrong user.

Checks:

  • Authenticated responses are not stored in shared public caches.
  • Cache-Control and Vary headers make sense for sensitive GETs.
  • CDN configuration does not key only on URL without auth context.
  • File download links are user-bound or short-lived signed URLs.

If User B receives User A content after a first authorized fetch by A, treat it as broken access control with infrastructure contributors.

Pagination and Aggregate Leakage

List endpoints can break access control without classic IDOR.

Examples:

  • Global search returns other tenant hits.
  • totalCount reveals existence of hidden resources.
  • Sorting by private fields errors in ways that leak data.
  • Analytics endpoints aggregate across tenants incorrectly.

Test with carefully seeded data so leakage is obvious.

Rate Limits Are Not Access Control

Throttling an admin endpoint does not mean a user is unauthorized. Confirm true authz denials, not only slow success.

Temporary Permissions and Time Boxes

Modern products grant temporary access:

  • Support break-glass sessions
  • Time-limited shares
  • Trial admin privileges
  • Scheduled access reviews

Test:

  1. Access works during the window.
  2. Access ends after expiry without waiting for UI refresh myths.
  3. Extensions are authorized.
  4. Audit logs capture temporary elevation if required.

Checklist Addition: Change Management Actions

High risk actions often missing from suites:

[ ] Transfer ownership
[ ] Move object between orgs
[ ] Bulk reassign
[ ] Merge accounts
[ ] Disable SSO for org
[ ] Rotate org-wide API keys
[ ] Change billing owner

These actions deserve explicit matrix rows because impact is huge.

Sample Automation Pseudocode

test cross_user_invoice_read_denied:
  tokenA = login(userA)
  tokenB = login(userB)
  inv = create_invoice(tokenA)
  res = get("/invoices/" + inv.id, tokenB)
  assert res.status in {403, 404}
  assert userA.email not in res.body
test user_cannot_list_admin_users:
  token = login(standard_user)
  res = get("/admin/users", token)
  assert res.status in {401, 403}

Keep these tests boring and always on.

Coordinating with Bug Bounty and Pentests

When external researchers report BAC:

  1. Reproduce with your fixtures.
  2. Fix the path and search for sibling paths.
  3. Add regression.
  4. Ask: is this a missing framework control or one omitted handler?
  5. If systemic, schedule a horizontal review of all handlers for that resource type.

External reports are free curriculum for your internal matrix.

Metrics for BAC Health

Track:

  • Number of production BAC incidents
  • Number of staging BAC bugs found by QA pre-release
  • Percent of new endpoints with explicit authz tests
  • Time to fix critical IDOR
  • Recurrence of same resource-type failures

If QA finds zero BAC issues all year on a complex multi-tenant app, your tests are probably too weak, not the product too perfect.

A Closing Drill You Can Run Tomorrow

Ninety-minute drill:

  1. Pick one resource type.
  2. Create objects as UserA.
  3. Attempt five unauthorized actions as UserB.
  4. Attempt two admin actions as UserB.
  5. Attempt one export.
  6. Document results in matrix form.
  7. File defects or residual risk notes.

Run this drill every time a new resource type ships. That habit is the operational answer to how to test for broken access control at team scale.

Conclusion

Learning how to test for broken access control means proving that every valuable action is limited by server-side rules for every actor. Build a permission matrix, pair users for horizontal tests, replay privileged functions for vertical tests, attack list/export/file paths, and never trust a hidden button as a control.

Broken access control is common because products change faster than permission checks. Make cross-user and cross-role denials part of ordinary regression, and you will catch the highest impact web security failures before attackers or customers do.

FAQ

Questions testers ask

How do you test for broken access control?

Create users with different roles and ownership, then attempt to access objects and functions outside each user's rights by changing IDs, forced browsing to hidden URLs, and calling APIs directly. Confirm the server denies unauthorized reads and writes, not only that the UI hides buttons.

What is the difference between horizontal and vertical privilege escalation?

Horizontal escalation is acting as a peer user, such as reading another customer's invoice. Vertical escalation is gaining higher privilege, such as a standard user calling an admin API. Both are broken access control and both need dedicated test cases.

What is IDOR?

Insecure Direct Object Reference is a common access control failure where an app uses a user-supplied object ID without verifying authorization. Changing /invoices/1001 to /invoices/1002 may expose another user's data if checks are missing.

Why is broken access control so common?

Modern apps have many roles, tenants, object relationships, and API endpoints. UI restrictions are easy; server-side checks on every path are harder. New features often add routes faster than authorization tests, so gaps ship unnoticed.

Can automated scanners find broken access control reliably?

Not reliably by themselves. Access control depends on business rules and multi-user context. Scanners help with some patterns, but human-designed role matrices and cross-user API tests catch most BOLA/IDOR issues.

What should a broken access control bug report include?

Include two actors, tokens or sessions used, exact requests, object ownership, expected denial, actual success or data returned, impact, and whether the issue is read, write, or function-level. Add sanitized evidence and a retest plan.