IDOR, short for insecure direct object reference, is an access control vulnerability in which an application uses a client-supplied identifier to fetch an object without checking whether the requesting user is allowed to access it. In application security it is the textbook example of broken object-level authorization: changing invoice_id=1001 to 1002 in a request returns someone else’s invoice.
IDOR is not a flaw in identifiers themselves. Exposing a database key is harmless when the server verifies ownership on every request; the vulnerability is the missing authorization check. That is why the OWASP Top 10 groups it under Broken Access Control, the category ranked first in the 2021 edition, and why the OWASP API Security Top 10 lists it first as Broken Object Level Authorization.
How an IDOR vulnerability arises
An IDOR arises when the server trusts that a user will only request objects the interface showed them. Developers authenticate the session, then load the record by the identifier in the URL, body or header, and return it. The check that the record belongs to the session’s user, tenant or role is simply absent.
A typical vulnerable handler queries SELECT * FROM orders WHERE id = ? using the request parameter. A safe handler scopes the query to the authenticated principal, for example WHERE id = ? AND customer_id = ?, or calls a central authorization function before returning data. The difference is often a single condition, which explains why IDOR is so common and so easy to miss in code review.
Authentication does not prevent IDOR. Every IDOR exploit uses a valid, logged-in session; the attacker is a legitimate user who reaches beyond their own data. This is also why perimeter defenses such as web application firewalls rarely detect it: the requests look completely normal.
Variants and where identifiers appear
IDOR affects any object reference that a client can influence, not only numeric IDs in URLs. The table lists common forms and their typical impact.
| Variant | Example reference | Typical impact |
|---|---|---|
| Horizontal read | GET /api/users/4821/profile | Exposure of other users’ personal data |
| Horizontal write | PUT /api/addresses/77 with another user’s ID | Modification of other accounts |
| Vertical escalation | Changing role_id or accessing admin object IDs | Privilege escalation |
| File and document access | /download?file=statement_2291.pdf | Mass download of documents |
| Multi-tenant leakage | tenant_id in header or JWT claim not enforced | Cross-customer data breach |
| Mass assignment overlap | Hidden owner_id field in a request body | Taking ownership of objects |
| Blind IDOR | Action succeeds without returning data, such as deleting a record | Integrity loss without visible leak |
Sequential integers make enumeration trivial, since an attacker can script thousands of requests in minutes. Random UUIDs make guessing hard, but identifiers frequently leak through shared links, logs, emails or other API responses, so unpredictable IDs are defense in depth, not a fix.
IDOR compared with related application flaws
IDOR is often confused with injection or traversal flaws because all involve manipulated parameters. The key difference is that IDOR uses the application’s intended functionality with a different identifier, while the other flaws break parsing or path handling.
| Vulnerability | What the attacker changes | Root cause |
|---|---|---|
| IDOR | A valid object identifier | Missing object-level authorization |
| Path traversal | A file path with sequences such as ../ | Unsanitized file system paths |
| SQL injection | Query syntax inside input | Unparameterized queries |
| CSRF | Nothing; tricks the victim’s browser into sending requests | Missing request origin checks |
| SSRF | A URL the server fetches | Unrestricted outbound requests |
| Function-level access control failure | The endpoint or action itself | Missing role checks on functions |
Preventing IDOR in application design
The only reliable prevention is authorization enforced on the server for every object access. Effective patterns move that check into shared code so individual developers cannot forget it.
| Technique | How it helps | Limitation |
|---|---|---|
| Scoped queries | Every lookup filters by user or tenant | Must be applied consistently |
| Central policy layer | One authorization function or policy engine for all objects | Requires architectural discipline |
| Row-level security in the database | Database enforces tenant isolation | Complex with shared connection pools |
| Indirect references | Per-session maps from short tokens to real IDs | Adds state and complexity |
| Random identifiers (UUIDv4) | Prevents easy enumeration | Not authorization; leaked IDs still work |
| Automated authorization tests | Tests request objects as a second user | Coverage depends on test design |
Frameworks help when used deliberately: ORM scopes, policy classes and resource-based authorization in common web frameworks make deny by default the natural path. A strong rule of thumb is that no handler should load an object by ID alone.
Testing for IDOR
IDOR is found mainly through manual and semi-automated testing with multiple accounts, because scanners cannot know which objects should belong to whom. Testers create two users in separate roles or tenants and replay each user’s requests with the other user’s session.
- Map every endpoint that accepts an identifier, including headers, cookies, JSON bodies and GraphQL arguments.
- Swap identifiers between accounts and compare responses, status codes and side effects.
- Test all HTTP methods, since GET may be protected while PUT or DELETE is not.
- Check secondary objects, such as attachments or comments, that inherit ownership from a parent.
- Add regression tests for every confirmed finding.
A frequent mistake is protecting the web interface while the mobile app API or an older API version remains unchecked. Logging and alerting on users who access unusually many distinct object IDs can also reveal enumeration in production.
The short version
Imagine a hotel where every guest’s key card opens the building, and room numbers are printed on the doors. If the rooms themselves have no locks, any guest can walk into any room just by choosing a different number. IDOR is that missing lock on each individual room.
In software, the room number is an identifier in a web address or request. The fix is to check, every time, that the person asking actually owns what they ask for, not just that they are logged in.
Where it makes a difference
A tax preparation web app lets customers download past returns through a link such as /returns/download?id=58312. During a security assessment, a tester logs in with a test account, changes the number to 58311 and receives another customer’s full tax return, including income and identification details. Because IDs are sequential, a simple script could have retrieved hundreds of thousands of documents.
The development team fixes the handler to look up returns only within the authenticated customer’s records, adds a shared authorization helper used by all download endpoints and writes automated tests that attempt cross-account access. They also switch document links to random identifiers as an extra layer. A retest confirms that requests for other customers’ returns now fail with a not-found response, revealing nothing about whether the document exists.