Short answer
An insecure direct object reference (IDOR) happens when your app fetches or changes a record using an id from the request, without checking that the record belongs to the person asking. Change the number in the address and you see someone else’s invoice. It is the most common shape of broken access control, which OWASP ranks first in its Top 10:2025. Random ids make it harder to guess but do not fix it. The fix is a server side check, on every request, that ties the record to the signed in user.
What does an IDOR look like?
You sign in to an app and open an invoice at /invoices/1042. You change the address to /invoices/1043. If the app shows you somebody else’s invoice, that is an IDOR. The same thing happens out of sight in API calls: GET /api/orders?id=1043, a DELETE on /api/notes/88, a download link with a file id in it.
The OWASP cheat sheet on IDOR defines it as a vulnerability that “arises when attackers can access or modify objects by manipulating identifiers used in a web application’s URLs or parameters.” In API security the same flaw is called broken object level authorization, and it is the first entry, API1, in the OWASP API Security Top 10 2023.
It is not only reading. The same missing check lets a user change or delete someone else’s record, which is often worse: a leaked invoice is a privacy incident, while a silently edited bank detail on someone else’s invoice is fraud. Treat every action on a record, not just viewing it, as needing its own check.
How common is broken access control?
Very. In the OWASP Top 10:2025, broken access control holds first place, and OWASP reports that 100 percent of the applications in its contributed test data had some form of it. The category maps to 40 weaknesses in the CWE list and had the highest number of occurrences in the data.
That figure covers every kind of access control failure, not only IDOR, and it describes applications that were tested, which skews toward ones somebody worried about. Still, the direction is not in doubt: if you have not checked, the safe assumption is that at least one route is missing the check.
Why do AI coding tools write IDOR?
Because the request rarely mentions it. “Add a page where users can view an invoice” produces a route that loads an invoice by id and returns it. It works. You test it on your own invoice, which you are allowed to see, so the missing check never shows.
There is also a quieter confusion between two words. Authentication answers “who are you?” Authorization answers “may you do this to that?” Generated code is good at the first, because sign in has a standard shape. The second depends on your rules, which the model can only know if you say them. A route that requires a signed in user and then loads any record by id is authenticated and still wide open.
A route that requires sign in and then loads any record by id is authenticated and still wide open.
Do random ids fix it?
No. Switching from 1042 to a long random id makes guessing impractical, and OWASP recommends it as defence in depth. But ids leak: they appear in shared links, browser history, logs, emails, and in other API responses. The cheat sheet is explicit that “even with complex identifiers, access control checks are essential.”
Treat random ids as a seatbelt, not as brakes.
How do you find IDOR in your own app?
Make a map first, then test the map. Ask your coding tool: “List every route, server action and server function that reads, changes or deletes a record using an id or other identifier from the request. For each, show the line that checks the record belongs to the signed in user, or say that there is none. Do not change anything yet.”
Then test with two accounts, A and B:
- As A, copy the ids of B’s records (create them as B, note the ids) and request them directly: view, edit, delete.
- Check nested resources. A route like /projects/7/tasks/31 needs to confirm both that project 7 is yours and that task 31 belongs to project 7.
- Check file downloads and exports, which are often written separately and forgotten.
- Check updates that set ownership. Can A change the owner, team or role field on a record in the request body?
- Check list endpoints with filters. Does /api/orders?user=B return B’s orders?
Any success is a critical finding for that route. The guide stop one user reading another user’s data runs the same test step by step.
How do you fix it for good?
- Scope every query to the caller. Instead of “find invoice 1043”, ask for “invoice 1043 where the owner is the signed in user”. OWASP’s cheat sheet recommends exactly this: look up objects within those that belong to the current user.
- Take the user’s id from the verified session on the server, never from the request body or a query parameter.
- Put the check in one helper that every route uses, so a new route cannot quietly skip it.
- Add database rules as a second layer. If you use a hosted database with row level security, the policies catch what a route misses; see securing a browser-built app.
- Write a test per route that requests another user’s record and expects a refusal. OWASP’s API guidance puts it bluntly: do not deploy changes that make those tests fail.
Return the same response for “does not exist” and “not yours”, so the error itself does not confirm which ids are real.
Which routes are most often missed?
The main view and edit pages are the ones people think to check. The routes that are easy to miss are the ones nobody looks at twice:
- Export and download routes, such as “download as PDF” or “export to CSV”, often written in a later session than the page they belong to.
- Delete routes, because deleting is tested by deleting your own test data.
- Background and webhook handlers that accept an id from a payload and act on it.
- Admin pages that check the user is signed in but not that they are an admin.
- Search and autocomplete endpoints that return matching records from every account.
- Invite and sharing flows, where a user can add themselves to a team or project by changing an id.
- Older versions of an API left running after the app moved to a new one.
When you fix one, search for its siblings. An IDOR is rarely alone: the same prompt that produced one route without a check produced the others in the same session, in the same style. A fix to one route is a reminder to check every route that touches the same table.
Where does this fit in a security review?
Access control is the first thing to check in any app with accounts, because it is where real customer data leaks and because it is the flaw generated code is least likely to handle unprompted. The free Vibe Coding Security Checklist puts it at the top of the list with its severity and fix deadline, alongside the database, secrets and header checks. See also the glossary entries for IDOR and access control.
Sources
- A01:2025 Broken Access Control, OWASP Top 10:2025
- API1:2023 Broken Object Level Authorization, OWASP API Security Top 10 2023
- Insecure Direct Object Reference Prevention Cheat Sheet, OWASP Cheat Sheet Series