All articlesAttack Techniques

How to Test for IDOR Vulnerabilities in Your API

May 3, 2026·12 min read·Nautillo Pro Security Team

APIs expose IDOR vulnerabilities differently than traditional web apps — object references appear in URL paths, request bodies, and query parameters simultaneously, and the absence of a UI makes them easy to miss in code review. This guide covers a structured API-specific testing methodology with HTTP examples for every pattern.

Why APIs have a larger IDOR attack surface

In a traditional web app, object references are often buried in form fields or session state — visible to an attacker, but one layer removed. In a REST API, object references are first-class citizens of the URL path: /api/v1/invoices/8821, /api/users/me/documents/44, /api/orders/ORD-20240401-9923.

Every endpoint that accepts an identifier and returns or modifies a resource is a potential IDOR. In a typical API with 40–80 endpoints, that's a large surface. The access control check — "does this user own resource 8821?" — has to be correct on every single one.

The second difference is that APIs often expose more data per response than a rendered web page. A web page might show a user's name and email. The underlying API endpoint might return the full user object — password hash, internal flags, billing details, associated account IDs — because the developer assumed only authenticated internal clients would call it.

The four API IDOR patterns

1. Sequential integer IDs in URL paths

The most obvious pattern. The attacker increments or decrements the ID to access adjacent resources.

# Authenticated as user 1001, accessing own order

GET /api/orders/5523 HTTP/1.1

Authorization: Bearer <user-1001-token>

# Test: access another user's order by changing the ID

GET /api/orders/5522 HTTP/1.1

Authorization: Bearer <user-1001-token>

# Vulnerable: returns 200 with another user's order data

# Secure: returns 403 Forbidden or 404 Not Found

Sequential IDs are common in databases using auto-increment primary keys. The predictability makes enumeration trivial — an attacker doesn't need to guess, just iterate.

2. GUIDs and UUIDs that aren't actually random

Many developers treat UUIDs as a security control — "the attacker can't guess a UUID." This is only true for cryptographically random UUIDs (v4). UUID v1 encodes the MAC address and timestamp and is partially predictable. Some systems generate UUIDs deterministically from user input, making them fully predictable once the pattern is known.

# UUID v1 example — timestamp-based, partially predictable

GET /api/reports/550e8400-e29b-11d4-a716-446655440000

# Test: check UUID version and generation pattern

# If IDs from the same time window share a prefix,

# the remaining entropy may be low enough to enumerate

The test here is not to guess one UUID — it's to determine whether the UUID generation is predictable enough to enumerate. Collect multiple UUIDs from your own account and check whether they follow a pattern in the timestamp or node components.

3. Object references in request bodies

IDOR isn't only in URL paths. APIs that accept object IDs in POST/PUT/PATCH request bodies are equally vulnerable. This pattern is common in relationship operations — assigning a document to a project, adding a user to a team, attaching a payment method to an order.

# Updating a document — attacker controls document_id in body

PATCH /api/documents/update HTTP/1.1

Authorization: Bearer <attacker-token>

Content-Type: application/json

{

  "document_id": 7743,

  "title": "Attacker-controlled title"

}

# document_id 7743 belongs to a different user

# Vulnerable: modifies another user's document

4. Indirect references — email, username, slug

Not all object references are numeric IDs. APIs that accept an email address, username, or URL slug as a lookup key are vulnerable when the application doesn't verify that the requesting user is authorized to access the referenced object.

# Fetching a profile by username

GET /api/profile?username=victim@example.com

Authorization: Bearer <attacker-token>

# Should return only public profile fields

# Vulnerable if it returns private fields (phone, address, payment info)

Step-by-step API IDOR testing methodology

1

Map every endpoint that accepts an object reference

Go through the API documentation or intercept traffic with a proxy. List every endpoint where a user-supplied value (path param, query param, request body field) is used to look up or modify a resource. This is your test surface.

2

Create two separate test accounts

Account A and Account B must be completely separate — different email addresses, no shared data. Create a resource with Account A (an order, document, report, etc.) and note its ID. You'll test whether Account B can access it.

3

Test horizontal access — same role, different user

Authenticated as Account B, attempt to read, modify, and delete the resource created by Account A. Send the request with Account B's token but Account A's resource ID. A 200 or 204 response is a confirmed IDOR. A 403 or 404 is correct.

4

Test vertical access — lower privilege accessing higher privilege resources

If the API has roles (admin, manager, user), test whether a lower-privileged account can access admin-only resources by direct reference. Create a resource as an admin, then attempt to access it with a regular user token.

5

Test write operations separately from reads

Many APIs correctly restrict GET access but forget to check authorization on PUT, PATCH, or DELETE. A resource that returns 403 on GET might accept a PATCH request with no authorization check. Test every HTTP method for every identified endpoint.

6

Check what the response reveals

Even if a 403 is returned, check whether the error response reveals information about the resource (existence, owner, type). A 404 for non-existent resources and 403 for unauthorized access leaks less information than a 403 that includes resource metadata.

What a complete HTTP proof of concept looks like

A confirmed IDOR finding needs to include the full request and response — not just a description. This is what you'd include in a security report or bug bounty submission:

REQUEST (authenticated as Account B)

GET /api/v1/invoices/8821 HTTP/1.1

Host: api.example.com

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...<Account-B-token>

Accept: application/json

RESPONSE

HTTP/1.1 200 OK

Content-Type: application/json

{

  "id": 8821,

  "user_id": 1047,  // Account A's user ID

  "amount": 4200.00,

  "status": "paid",

  "card_last4": "4242",

  "billing_email": "victim@example.com"

}

This format — full request headers, full response body, both account identifiers clearly labelled — gives developers everything they need to reproduce and fix the issue immediately.

What secure API authorization looks like

The fix is always the same: verify ownership server-side on every request, not just at the route level. The resource lookup should include the authenticated user's ID as a condition, not just the object ID from the request.

Vulnerable

const invoice =

  await db.invoices

    .findById(

      req.params.id

    );

// No ownership check

Secure

const invoice =

  await db.invoices

    .findOne({

      id: req.params.id,

      userId: req.user.id

    });

The secure version pins the query to the authenticated user's ID. If the invoice doesn't belong to the requesting user, the query returns null and you return 404 — leaking no information about whether the resource exists at all.

Common mistakes that introduce IDOR after it's been fixed

New endpoints inherit the data model but not the authorization check

A developer adds a new /export or /download endpoint for an existing resource. The authorization check lives on the original GET endpoint — the new one gets written quickly and the ownership check is forgotten.

Middleware that only checks authentication, not authorization

Auth middleware confirms a valid JWT is present and sets req.user. It doesn't verify that req.user.id matches the resource owner. Developers assume the middleware handled authorization when it only handled authentication.

Admin endpoints accidentally reachable by regular users

An internal admin API is built without authorization checks because 'it's internal.' The endpoint gets routed through the same API gateway as user-facing endpoints. No network-level restriction prevents a regular user from calling it directly.

IDOR reintroduced during a refactor

Authorization logic is moved during a refactor. In the process, a resource lookup is extracted to a shared helper that doesn't include the ownership condition. All endpoints using the helper become vulnerable simultaneously.

Test your API for IDOR automatically

Nautillo Pro runs automated IDOR simulations against your API — testing horizontal and vertical access control across all discovered endpoints. Every confirmed finding includes the exact HTTP request and response proving unauthorized access.