API Security Testing: 7 Checks for Access Control Gaps

API security testing checks whether each caller can access only the data and actions their permissions allow.

Start with a written permissions map, approved test accounts, and test-owned records. Then run these seven checks (part of a broader OWASP API Top 10 checklist):

  1. Missing or invalid credentials: Protected requests must return no private data and make no changes.
  2. Cross-user access: One user must not read, change, or delete another user’s private records.
  3. Cross-tenant access: Requests must stay within the caller’s allowed tenant boundaries.
  4. Restricted functions: Lower-privilege roles must not run admin-only actions.
  5. Field visibility: Responses must exclude fields the caller cannot read.
  6. Protected fields: Create and update requests must not save field changes the caller cannot make.
  7. Alternate paths: Methods, routes, versions, and GraphQL resolvers must enforce the same permissions.

A status code alone does not prove a check passed. Inspect response data, verify changes from the owner’s account, and save request-response records. Report only confirmed gaps, then retest fixes alongside allowed access.

Checks 1 to 3: Test Protected Access and Data Isolation

1. Block Protected Access Without Valid Credentials

Replay protected invoice read, update, and delete requests without a token. Then repeat with malformed, expired, and revoked tokens. For revoked tokens, retest after logout or a password change. Expect 401 Unauthorized or 403 Forbidden, with no sensitive data or side effects. (sources: IDOR testing guide, OWASP API Security Top 10 guide)

Check the full response, not just the status code. A 404 alone doesn't prove access was blocked. Compare it with a valid authenticated response, and verify that the attempted update left stored data unchanged. For GraphQL, inspect both data and errors: a denied request should return null or an error, never protected data. (sources: IDOR testing guide, our dev.to IDOR example, GraphQL security testing) If the request remains exposed, move to ownership checks.

2. Block Cross-User Object Access

If authentication fails open, repeat the same requests as another user to check object ownership.

Create a private invoice as User A. Then, as User B with the same role, replay the read, update, and delete requests against User A's object ID. Expect 403 Forbidden, or 404 Not Found if the API intentionally hides the object's existence.

Record both users, the object ID, and the request-response pairs. After write or delete attempts, check from the owner's session that nothing changed. Any disclosure or persisted change is an authorization failure. Unpredictable IDs aren't a substitute for ownership checks. (sources: IDOR testing guide, our dev.to IDOR example)

3. Block Cross-Tenant Data Access

Keep the same requests, but switch tenants to check data isolation.

Use Tenant Beta's session and send Tenant Alpha IDs in paths, query parameters, JSON bodies, and tenant headers. Test retrieval, search, export, update, and delete operations. Denied requests must expose no Alpha records, identifiers, or counts, and must make no changes. A list response passes only if it contains data Beta is allowed to access. (sources: multi-tenant isolation testing guide)

Also check pagination, cached responses, async jobs, and webhook deliveries. Blocking the main endpoint isn't enough if another path exposes Alpha's data. Record each job or export identifier alongside its originating request so you can trace delayed disclosure back to the test. (sources: multi-tenant isolation testing guide)

Checks 4 to 6: Test Function and Field Permissions

After checking object and tenant isolation, test what each role can do and which fields it can read or change.

4. Block Restricted Functions for Lower-Privilege Users

Capture a request for a restricted function, then replay it with a standard-user token, even if the interface hides the action. The server should deny it, normally with 403 Forbidden, and return no protected data or cause any side effects.

Save the request and response. Then use an authorized read-back to confirm that nothing changed. Repeat for other lower-privilege roles and across bulk, batch, and versioned routes. Hidden UI controls do not prove authorization. (sources: OWASP API Security Top 10 guide, GraphQL security testing)

5. Hide Fields the User Cannot Read

Permission to access a record doesn't mean permission to read every field. Request the same allowed record with admin and standard-user tokens, then compare the payloads against documented field permissions. Include nested properties, expansions, and field selectors.

Restricted billing details or internal notes should be omitted, returned as null if that's the intended design, or denied. Check list, export, error, and GraphQL responses, too. Assess a field's sensitivity by its business context, not just its name. (sources: OWASP API Security Top 10 guide, GraphQL security testing)

6. Prevent Changes to Protected Fields

Test whether the API accepts fields the caller should not control.

Add protected properties such as role, owner_id, tenant_id, status, or price to valid create and update requests.

Include nested objects, patch inputs, and bulk items. The server should reject the change or ignore it without saving it. This tests for mass assignment: whether the server accepts fields the caller isn't allowed to control. (sources: OWASP API Security Top 10 guide, guides index)

Compare the original values, submitted changes, and responses, then use an authorized read-back to check what was stored. Report a gap only when the evidence shows an unauthorized stored change. (sources: IDOR testing guide, our dev.to IDOR example)

Check 7: Test Alternate Paths and Review Results

After testing object, tenant, function, and field access, check that alternate routes enforce the same permissions.

7. Enforce Permissions Across Methods and Routes

Test every supported GET, POST, PUT, PATCH, and DELETE route for each protected object type. This checks for bypasses missed in earlier tests: equivalent actions must enforce the same policy across methods, routes, and versions. A 405 Method Not Allowed response means the method isn’t supported, not that authorization passed. (sources: IDOR testing guide, our dev.to IDOR example)

Cover detail, bulk, export, versioned, file, and image routes. Check identifiers passed through query strings, JSON body fields, headers, and cookies. For GraphQL, test each resolver. For tenant isolation, retest alternate paths that return the same records. (sources: IDOR testing guide, OWASP API Security Top 10 guide, multi-tenant isolation testing guide, GraphQL security testing)

Apply the same permission matrix to every route variant and log the actual results. Inspect returned fields and error bodies, not just status codes.

Permission matrix: expected decision and recorded evidence for each route variant
ActorResourceActionMethodRouteExpected decisionObserved dataSide effects
User AUser B’s objectReadGETDetail routeDenyRecord body and fieldsRecord any state change
User AUser B’s objectReadPOSTBulk-read routeDeny access to B’s objectCheck each returned itemRecord any state change
User AUser B’s exportDownloadGETExport download routeDenyInspect downloaded contentRecord job activity
User AUser B’s objectUpdatePATCHVersioned routeDeny changeRecord response and errorsOwner read-back shows whether state changed

Classify Results and Document Fixes

Record the actor, resource, action, expected decision, observed result, status, and timestamped evidence. Mark a result as confirmed failure only when evidence proves a read without permission, a stored change without permission, or execution of a restricted function. Keep intended access, passed controls, inferred bypasses, and untested paths separate. Put unclear responses and unavailable routes under limitations, not confirmed impact. (sources: IDOR testing guide, multi-tenant isolation testing guide, methodology)

Recommend centralized server-side checks for ownership, tenant access, and function permissions. Add regression tests for every affected method and route. Retest the original request pair, rerun no-token and nonexistent-ID controls, and check sibling routes. Send uncertain policy decisions and incomplete evidence for manual review. (sources: IDOR testing guide, multi-tenant isolation testing guide, our dev.to IDOR example)

Conclusion: Repeat the Checks and Verify Fixes

Repeat the checks against the documented permissions model. Retest the same roles, tenants, routes, and methods.

Verify both denied requests and legitimate access. Use controlled records and save an owner-session baseline. Confirm that writes without permission leave records unchanged, while permitted writes still succeed.

Once you’ve confirmed the evidence, rank fixes by impact: confirmed exposure, changes made without permission, and actions taken without permission. Before closing a finding, check sibling routes and alternate methods. Add automated two-account matrix tests for new routes and object types, and use that same matrix as a regression gate.

If testing capacity is limited, scope the assessment to the relevant roles, tenants, and routes. Include verified findings, guidance on fixes, testing limits, and fix verification.

FAQs

How do I test APIs with undocumented permissions?

Find hidden endpoints by comparing official API documentation with client-side code and network traffic, or by probing older API versions.

Use the two-account method to test each route with a low-privilege token. Check whether the response returns data or completes a state change that the account shouldn't be allowed to access or perform.

Include control requests using a nonexistent ID, an anonymous session, and the correct owner’s token. These checks help separate authorization failures from intended behavior.

Which access control checks should I prioritize?

Prioritize authorization testing at the object level (BOLA/IDOR), function level, and property level. Sign in as User A, then use User B’s resource identifiers to check whether User A can access or modify those resources.

For each object type, test every HTTP method and all read, write, delete, and share operations. After each change, retrieve the object as its owner to confirm whether the unauthorized change persisted. Use nonexistent IDs and anonymous requests as controls to check your results.

How can I safely automate authorization tests?

Stay within the approved scope and use test-owned accounts and data. Record a baseline, then replay the same request from a second account to check for cross-user or cross-tenant effects. Verify destructive changes by reading back the results, not just relying on “200 OK.” Test nonexistent IDs, anonymous or unsigned tokens, and wrong or invalid signatures.

Report only confirmed effects. Save requests, responses, and timestamps as evidence, and retest fixes using the same evidence checks. (sources: IDOR testing guide, OWASP API Security Top 10 guide, multi-tenant isolation testing guide)