Blog / Authorization Testing

How to Test for IDOR in REST APIs: A Step-by-Step Guide

Insecure direct object references are the most common serious API vulnerability, and the simplest to exploit: change an ID, get someone else’s data. This guide shows how to test for IDOR methodically across every endpoint, method, and identifier location.

· 6 min read

An IDOR (insecure direct object reference) happens when an API uses an identifier supplied by the client to fetch or modify an object, but never checks whether the caller is allowed to access that object. OWASP calls the API version of this flaw Broken Object Level Authorization (BOLA) and ranks it first in the OWASP API Security Top 10.

This article was created with AI assistance. Only test systems you own or are explicitly authorized to test, and use non-production data.

What does an IDOR look like?

Alice owns order 5001. Bob owns order 5002. Both are ordinary customers.

GET /api/orders/5002 HTTP/1.1
Host: staging.example.com
Authorization: Bearer <alice-token>

HTTP/1.1 200 OK
Content-Type: application/json

{"id":5002,"customerId":"bob","shippingAddress":"...","items":[...]}

Alice is authenticated, so the request passes the login check. The handler loads order 5002 by ID and returns it, without asking whether Alice owns it. That missing ownership check is the whole vulnerability.

IDOR is hard to catch for two reasons. First, the request is completely valid—no payload, no malformed input. Second, a response of 200 OK is correct for Bob and wrong for Alice, so a tool that tests as a single user has no way to tell the difference.

Step 1: Set up at least two users per role

You need a victim and an attacker who share the same role, plus a second tenant if your product is multi-tenant:

  • User A (attacker) and User B (victim), same role, same tenant.
  • User C in a different tenant or organization.
  • One user for each additional role (viewer, editor, admin) if you will also test function-level access.

Have each user create the objects you will test: orders, invoices, documents, projects, API keys, payment methods. Record every ID. You cannot test access to an object that does not exist.

Step 2: Inventory every identifier

Go through your OpenAPI spec or Postman collection and list every place the client sends an identifier. Do not stop at path parameters:

LocationExample
PathGET /orders/{orderId}
Query stringGET /invoices?customerId=812
JSON body{"accountId": 812, "amount": 50}
Nested body{"order": {"shipping": {"addressId": 77}}}
HeaderX-Account-ID: 812
Batch endpoints{"ids": [5001, 5002, 5003]}
File pathsGET /files/download?path=users/812/report.pdf

A well-described OpenAPI spec makes this step fast. See how to write an OpenAPI spec that makes security testing easier.

Step 3: Build the test matrix

For each operation, write down who should be able to call it on which objects. A simple matrix is enough:

OperationUser A on B’s objectUser C (other tenant)No token
GET /orders/{id}DenyDeny401
PATCH /orders/{id}DenyDeny401
DELETE /orders/{id}DenyDeny401
GET /orders?customerId=Own orders onlyOwn orders only401
POST /orders/{id}/refundDenyDeny401

“Deny” can be 403 Forbidden or 404 Not Found. Many teams prefer 404 so attackers cannot confirm that an object exists.

Step 4: Swap identifiers and compare responses

  1. Send User A’s legitimate request for A’s own object and save the response as the baseline.
  2. Replace A’s object ID with B’s, keep A’s token, and send the request.
  3. Compare. A 2xx response that contains B’s data is a confirmed IDOR. A 2xx with an empty body or A’s own data needs a closer look—some APIs silently ignore the foreign ID.
  4. Repeat with User C’s IDs to test tenant isolation.
  5. Repeat with no token to confirm authentication is enforced at all.

Status codes alone can mislead. Compare response bodies: look for B’s identifiers, names, or emails in the response to User A.

Step 5: Test the variants that scanners and humans miss

  • Every HTTP method. A protected GET /orders/5002 next to an unprotected PUT or DELETE on the same path is one of the most common IDOR patterns.
  • Write operations that reference other objects. Can User A attach B’s payment method or address to A’s own order?
  • List and search endpoints. Filters such as ?customerId= or ?ownerId= often trust the client.
  • Export and report endpoints. PDF and CSV generators frequently bypass the authorization used by the main API.
  • Parameter pollution. Send both IDs: ?id=5001&id=5002, or one in the path and another in the body.
  • Type confusion. Send the ID as a string, number, or array: "5002", 5002, [5002].
  • Older API versions. The /v1 handler may lack the fix applied to /v2.
  • Indirect references. Objects reached through a parent: /projects/{A’s project}/documents/{B’s document}.

Step 6: Confirm impact safely

For read operations, confirmation is simple: the response contains another user’s data. For write and delete operations, be careful:

  • Target objects you created for testing, never real customer data.
  • Prefer harmless changes (a note field) and read the object back as its owner to confirm the change.
  • For deletes, create a disposable object specifically for the test.
  • Record the exact request, response, users, and timestamps for the report.

Do UUIDs prevent IDOR?

No. Random identifiers make IDs harder to guess, but they do not add an authorization check. UUIDs leak constantly: in URLs shared by email, in list responses, in logs, in webhook payloads, and in other API responses that include related objects. Treat unguessable IDs as defense in depth, never as the control. When testing, harvest IDs from responses User A can legitimately see and try them in other operations.

Step 7: Automate IDOR testing

Manual IDOR testing does not scale: operations × methods × identifier locations × users quickly reaches thousands of requests, and every new endpoint can reintroduce the flaw. Automation needs three inputs that a generic scanner lacks:

  1. The full list of operations and their parameters (from OpenAPI or Postman).
  2. Credentials for multiple users and roles, with automatic sign-in and token refresh.
  3. Ownership context: which objects belong to which user, so the scanner knows which IDs are foreign.

DastPilot is built around those inputs. You define roles and link each one to an authentication profile and ownership identifiers; DastPilot then replays operations across users, compares the responses, and reports BOLA and BFLA findings with the exact HTTP exchanges. Because results are compared across runs, a fixed IDOR that reappears is flagged as reopened. For the function-level side of the problem, read BFLA vs BOLA, and for multi-tenant products see how to test tenant isolation.

How to fix IDOR vulnerabilities

  • Scope queries by the authenticated principal. Load WHERE id = :id AND owner_id = :currentUser (or tenant) instead of loading by ID and checking afterward.
  • Centralize authorization. Use a policy layer or middleware so new endpoints inherit the check by default.
  • Never trust client-supplied ownership. Derive user and tenant from the verified token, not from body fields or headers.
  • Return 404 for objects the caller cannot access to avoid confirming that they exist.
  • Add regression tests for every fixed IDOR, and run role-aware scans on every release.

Frequently asked questions

What is the difference between IDOR and BOLA?

They describe the same flaw. IDOR (insecure direct object reference) is the older, general web term. BOLA (Broken Object Level Authorization) is the name OWASP uses for it in the API Security Top 10, where it ranks first.

Can automated scanners find IDOR?

Only if they test as more than one user and know which objects each user owns. Single-user scanners cannot distinguish a legitimate 200 response from an unauthorized one. Role-aware DAST tools such as DastPilot replay requests across users to detect IDOR automatically.

Is a 200 response always an IDOR?

No. Some APIs ignore a foreign ID and return the caller’s own data, or an empty result. Confirm by checking whether the response body contains the other user’s data, or whether a write actually changed their object.

Do random UUIDs fix IDOR?

No. UUIDs make guessing harder but leak through URLs, responses, and logs. The fix is a server-side authorization check on every object access.

Should an unauthorized object return 403 or 404?

Both deny access. Returning 404 avoids confirming that the object exists, which many teams prefer for user-owned resources. Be consistent across the API.

Find IDOR across every endpoint automatically

DastPilot tests every operation as every configured user and reports BOLA with the exact requests and responses. 30-day free trial, no credit card.

Start Free Trial