Insecure direct object references (IDOR) are a type of access control vulnerability where an application exposes internal object identifiers – such as user IDs, order numbers, or file names – without verifying whether the requesting user is authorized to access them.

IDOR is no longer a standalone category in modern standards. In the OWASP Top 10 2025, it falls under A01: Broken Access Control, which is the most critical category of web application risk. That shift reflects reality: IDOR is not a niche issue but one of the most common and impactful ways attackers access unauthorized data.

In modern applications, especially API-driven systems, IDOR remains a frequent source of data exposure because object references are everywhere – and authorization checks are often inconsistent.

What does IDOR stand for?

IDOR stands for Insecure Direct Object Reference.

A direct object reference is any identifier used by an application to access a resource, which can include database IDs, filenames, API resource identifiers, and UUIDs or tokens. IDOR happens when such references are directly exposed to users and the application does not verify whether the user is allowed to access the referenced object.

Why are IDOR vulnerabilities dangerous?

IDOR vulnerabilities are dangerous because they provide direct access to sensitive data with minimal effort.

Unlike injection flaws or memory corruption bugs, IDOR does not require advanced exploitation techniques. An attacker often only needs to modify a request and observe the response. IDOR impacts can include:

  • Unauthorized access to other users’ data
  • Exposure of financial records, personal information, or internal documents
  • Privilege escalation and account takeover
  • Large-scale data harvesting through enumeration

In API-driven architectures, the risk is even higher. APIs often expose large volumes of structured data and serve as the primary interface for mobile apps and frontend clients. If object-level authorization is missing, attackers can bypass the UI entirely and interact directly with backend services to perform unauthorized operations or exfiltrate sensitive data in bulk.

How do attackers exploit IDOR vulnerabilities?

At a high level, exploiting IDOR follows a simple pattern:

  1. Identify an object reference in a request
  2. Modify that reference
  3. Send the modified request
  4. Analyze the response

Say an attacker observes the following API request to fetch order data by ID:

GET /api/orders/74656
Authorization: Bearer <token>

The attacker can then modify the object ID and send the modified request:

GET /api/orders/74657
Authorization: Bearer <token>

If the application returns data for order 74657 without verifying ownership, the attacker may gain unauthorized access to order data. Attackers may repeat this process to enumerate large datasets or target specific users.

Common IDOR examples in web apps and APIs

The following examples show how IDOR vulnerabilities can manifest in real-world code and how to fix them.

Example 1: Classic URL parameter IDOR

Here’s vulnerable PHP code that runs a database query to fetch transaction data by ID:

$stmt = $db->prepare("SELECT * FROM transactions WHERE id = ?");
$stmt->execute([$_GET['id']]);
$result = $stmt->fetchAll();

It’s vulnerable because the query retrieves data based only on the id value and there is no check that the transaction is owned by the current user.

A more secure version would also check the user ID to verify ownership:

$stmt = $db->prepare("SELECT * FROM transactions WHERE id = ? AND user_id = ?");
$stmt->execute([$_GET['id'], $_SESSION['user_id']]);
$result = $stmt->fetchAll();

This type of check ensures that users can only access their own transactions.

Example 2: IDOR (BOLA) in a REST API

Here’s a similar Node.js (Express) example that calls a REST API to fetch order data by ID:

app.get('/api/orders/:id', async (req, res) => {
  const order = await db.orders.findById(req.params.id);
  res.json(order);
});

The problem with this code is that the application currently returns any order by ID, with no validation against the authenticated user.

A more secure version also checks the user ID:

app.get('/api/orders/:id', async (req, res) => {
  const order = await db.orders.findOne({
    _id: req.params.id,
    userId: req.user.id
  });

  if (!order) return res.status(404).send('Not found');
  res.json(order);
});

Example 3: IDOR directly in the request body

Object references can sometimes also be found in JSON data blocks in application requests. This POST request to update a user profile with a new email includes the current user’s ID:

POST /api/update-profile
Content-Type: application/json

{
  "user_id": 123,
  "email": "attacker@example.com"
}

If the server takes the user_id value from the request without verification, an attacker may be able to simply change the ID and then modify data in another user’s profile.

Here’s a more secure way to check the user ID:

app.post('/api/update-profile', async (req, res) => {
  const userId = req.user.id;
  await db.users.updateOne(
    { _id: userId },
    { $set: { email: req.body.email } }
  );
  res.sendStatus(200);
});

The server simply ignores any client-supplied identifier and uses the authenticated user context instead.

Example 4: GraphQL IDOR

GraphQL APIs often expose flexible queries that allow clients to request objects directly by ID. If resolvers do not enforce object-level authorization, this can lead to IDOR-style vulnerabilities.

In this vulnerable example, the application allows querying user records by ID without enforcing authorization. The resolver returns any user by ID, there is no authorization check, and any authenticated user can query arbitrary user records:

const resolvers = {
 Query: {
   user: async (_, { id }, context) => {
     return await db.users.findById(id);
   }
 }
};

Here’s a more secure version that also checks the ID for presence and object-level authorization:

const resolvers = {
 Query: {
   user: async (_, { id }, context) => {
     const user = await db.users.findById(id);
     if (!user) {
       throw new Error("Not found");
     }
     if (user.id !== context.user.id) {
       throw new Error("Unauthorized");
     }
     return user;
   }
 }
};

Example 5: File access IDOR

If an app provides downloadable invoices within a user account, the easiest and most insecure way to do it is through a direct download request like:

GET /download?file=invoice_74656.pdf

This classifies as IDOR because you’re exposing the object reference (filename, in this case) and not checking authorization. An attacker may be able to download other users’ invoices simply by requesting different filenames:

GET /download?file=invoice_74657.pdf

A more secure version checks the user ID first:

const path = require('path');

app.get('/download', async (req, res) => {
  const file = await db.files.findOne({
    filename: req.query.file,
    userId: req.user.id
  });

  if (!file) return res.status(404).send('Not found');

  const safeFilename = path.basename(file.path);
  res.download(path.join('/safe/storage/directory', safeFilename));
});

In addition to validating ownership, this example also normalizes the filename and securely builds the full download path to prevent directory traversal attacks and also avoid directly serving paths stored in the database.

IDOR vs BOLA – what’s the difference?

IDOR and BOLA (broken object-level authorization) refer to the same underlying issue: missing authorization checks for specific application objects. The difference is mostly contextual:

  • IDOR is the general term used in web application security
  • BOLA is the API-specific term used in modern API security

In practice, BOLA describes how IDOR manifests in APIs, where object identifiers are commonly passed in paths, query parameters, or request bodies.

Why are IDOR vulnerabilities hard to detect?

IDOR vulnerabilities are harder to detect than many other issues because they depend on context.

Key challenges for identifying IDOR include:

  • User context: A request may be valid for one user but not another
  • Business logic: Authorization rules vary across endpoints
  • Statefulness: Access may depend on workflows or sequences
  • Hidden attack surface: Many object references exist only in APIs

For example, a request may return technically valid data, but without the business logic to know who should own that data, it is difficult to determine whether access is authorized. You also get more complex authorization scenarios – such as multi-step workflows or role-based edge cases – that typically require manual validation.

How do you test for IDOR vulnerabilities?

Manual testing for IDOR

The most common and reliable but also the slowest method is manual testing using multiple accounts:

  1. Log in as User A
  2. Capture a request
  3. Log in as User B
  4. Replay the request
  5. Compare responses

If User B can access User A’s data without authorization, the application is vulnerable.

What modern DAST tools can do to find IDOR

Modern dynamic application testing (DAST) tools can automate parts of this process by:

  • Performing authenticated scans
  • Discovering and testing APIs
  • Manipulating parameters in requests
  • Analyzing responses for anomalies
  • Running and comparing scans with different credential sets

While complex authorization logic may still require validation, automated tools can help identify many IDOR-like issues, especially in predictable patterns such as ID-based resource access.

Acunetix applies these runtime testing techniques to live applications and APIs. By scanning authenticated areas, exercising API endpoints, and modifying request parameters, it helps uncover authorization weaknesses that may manifest as IDOR or BOLA vulnerabilities in real-world conditions.

How do you prevent insecure direct object references?

Preventing IDOR requires consistent and enforced authorization at every layer of the application.

Enforce object-level authorization

Every application request must verify who is making the request and whether they can access the specific object being requested. This should be an architectural requirement that’s built into the app rather than bolted on later.

Validate on the server side only

Never trust client input for authorization decisions. Always derive identity from the authenticated session.

Use precisely scoped queries

It’s often easier to fetch a broad set of resources instead of making upfront authorization decisions, but this can open the way to IDOR. An insecure example:

SELECT * FROM orders WHERE id = ?

If the ID is exposed to the user somewhere, this query will do nothing to prevent unauthorized access. A better approach would be to bake user authorization into the database query:

SELECT * FROM orders WHERE id = ? AND user_id = ?

Centralize authorization logic

Use consistent patterns such as middleware checks, service-layer validation, and policy-based access control to avoid auth gaps.

It’s important to distinguish between authentication and authorization here. Middleware is effective for enforcing coarse-grained access control (for example, ensuring a user is logged in), but it cannot enforce object-level authorization on its own. Access decisions that depend on specific resources – such as whether a user owns a record – must be enforced at the service or data-access layer, where the application has full context.

Common libraries to help do this include Pundit (Ruby on Rails), django-rules (Django), and Casbin or OPA for service-level authorization.

Do not rely purely on “unguessable” identifiers

UUIDs and random IDs reduce guessability but do not replace authorization checks. They are at best an additional layer of security to deter casual attackers and shouldn’t be your only IDOR protection.

Test continuously

Authorization flaws often appear as applications evolve and APIs change. Include automated IDOR testing in QA, security testing, and CI/CD pipelines.

See how Acunetix tests for real-world vulnerabilities

IDOR vulnerabilities are difficult to identify without testing applications in their running state and under realistic conditions. Acunetix helps security and development teams continuously test web applications and APIs, uncovering exploitable vulnerabilities and reducing time spent on manual verification.

Request a demo to see how Acunetix identifies real-world security issues in your applications – IDOR and beyond.

An example is changing a request from /api/orders/1001 to /api/orders/1002 and receiving another user’s data because the application does not verify ownership of the requested resource.

IDOR is a specific type of broken access control that occurs when applications fail to enforce authorization checks on individual objects or records.

IDOR is the traditional term used in web application security, while BOLA (Broken Object Level Authorization) is the API-focused term. Both describe the same underlying issue.

Automated tools can identify many IDOR-like patterns, especially in APIs and predictable ID-based access scenarios. However, complex cases involving business logic or multi-step workflows often require manual validation.

Developers fix IDOR by enforcing object-level authorization checks, validating access on the server side, and ensuring queries are scoped to the authenticated user.

No. UUIDs make identifiers harder to guess but do not replace proper authorization checks. Applications must still verify that users are allowed to access each object.

Because APIs often expose direct object references in URLs or request bodies and rely on backend logic for authorization, making it easier for missing checks to go unnoticed.

SHARE THIS POST
THE AUTHOR
Zbigniew Banach
Zbigniew Banach
Technical Content Lead & Managing Editor
Cybersecurity writer and blog managing editor at Invicti Security. Drawing on years of experience with security, software development, content creation, journalism, and technical translation, he does his best to bring web application security and cybersecurity in general to a wider audience.