IDOR prevention: stop users reading each other's records
IDOR prevention is about one question your server must ask every time: is this user allowed to touch this record? IDOR stands for insecure direct object reference. It is the bug where someone changes an ID in a request, say from 123 to 124, and gets another customer's profile, invoice or document. This guide follows the OWASP Insecure Direct Object Reference Prevention Cheat Sheet and ends with a test you can run today.

Three ingredients, one fix. Simplified from the OWASP IDOR Prevention Cheat Sheet.
The three ingredients of an IDOR
OWASP says every IDOR has three parts:
- An object: an account, a document, a support ticket, a transaction, a partner profile.
- A reference to it: an ID, a UUID, an account number, a token or a slug.
- A missing check: no object-level authorization that confirms this user may read or change this object.
Take away the third and the bug is gone, whatever the first two look like. That is why the cheat sheet puts its weight on the check, not on the identifiers. IDOR is one form of the wider problem in our post on broken access control.
Where object references hide
The classic example is a number in the address: /users/123. Change it to 124 and, if the app does not check, you see user 124.
OWASP shows two less obvious places:
- The request body. A profile form with a hidden
user_idfield. Edit the field before submitting, and without a server-side check you update someone else's profile. - File names.
/documents/annual-report.pdfchanged to/documents/financial-statement.pdf. The file name is the reference.
OWASP traces the same root cause in each case: the app did not check, on the server, whether this user had permission for that object before showing or changing it. Hiding a field or a link in the page does not count as a check, because the person sending the request controls everything in it.
So when you look for IDORs, look past numeric IDs. Account numbers, tokens, slugs and file names all count. APIs deserve the same attention; our REST API security checklist covers the endpoints side.
Why random IDs are not enough
A common response to an IDOR report is to switch from 123 to a long random ID. OWASP agrees that complex identifiers such as GUIDs can make guessing practically impossible. But it is firm that access checks are still essential.
IDs leak. They appear in shared links, emails, browser history, logs and other users' screens. If someone obtains the address of an object they should not see, the app must still refuse. Random IDs are defense in depth, an extra layer behind the check, never a replacement for it. The sheet also advises against encrypting identifiers, since doing that securely is hard.
If you do add random identifiers as that extra layer, OWASP gives two ways. Add a column of random strings to the table and use those in addresses instead of the numeric primary key. Or use UUIDs or other long random values as the primary keys themselves. Either way, keep the ownership check exactly where it was.
IDOR prevention in code
The cheat sheet's main fix is to check access for every object, on every attempt, and to build that check into the structure of your code using your framework's recommended approach.
The cleanest way is to look objects up only within what the user owns. OWASP's Ruby on Rails example:
- Vulnerable:
Project.find(params[:id])searches every project. - Safe:
@current_user.projects.find(params[:id])searches only this user's projects.
The Java example does the same with a query by ID and owner ID. A second option is to fetch the object and then compare its owner with the current user, refusing if they differ.
Two more rules from the sheet:
- Take the user from the session. Avoid putting user identifiers in URLs and request bodies where you can. Work out who is asking from session information, and keep the IDs of a multi-step flow in the session too, so they cannot be edited between steps.
- Hide existence when it matters. A fetch-then-check can reveal that a record exists even when access is refused. If that is sensitive, for example whether an account exists for an email address, use the scoped lookup and return the same response, such as 404, for "not found" and "not yours".
One framework note: Spring's @PreAuthorize only works once method security is turned on with @EnableMethodSecurity; OWASP notes Spring Boot does not turn it on by default. An annotation that never runs is a check that never happens.
Test with two accounts
OWASP's testing method is simple, and it works whether your IDs are guessable or not:
- Create User A and User B.
- Give each their own objects: documents, tickets, invoices.
- Sign in as User A and try to reach User B's objects by changing references in requests.
- Confirm the app refuses every time.
Do this for every kind of operation, not just viewing: read, create, update, delete, export and administrative actions. An app that blocks reading another user's invoice but lets you delete it still has an IDOR. If your app holds data for several customer companies, add a second organisation too; our post on multi-tenant security covers that case.
Worked example: an IDOR pass on an invoicing app
- List references. Open the browser's network panel and use the app as User A for ten minutes. Write down every ID, number, token or file name in a URL or request body. Say you find
/invoices/{id},/invoices/{id}/pdf,DELETE /invoices/{id}, acustomer_idin the create form, and/exports/{file}. - Swap. Repeat each request with one of User B's values. Note any response that is not a refusal.
- Fix in one place. Change each lookup to search only the current user's invoices, the scoped query pattern above. Take
customer_idfrom the session, not the form. - Same answer. Return 404 for both "no such invoice" and "not your invoice".
- Keep it fixed. Turn the swap requests into automated tests with two test users, so a new endpoint without the check fails the build.
Frequently asked questions
Do UUIDs prevent IDOR?
No. OWASP says complex identifiers make guessing hard, but access checks are still essential, because IDs can be obtained in other ways.
Should I return 403 or 404 for someone else's record?
If the existence of the record is sensitive, OWASP suggests returning the same response, such as 404, for missing and forbidden records.
Is IDOR only about user IDs in URLs?
No. References can be in request bodies, hidden fields and file names, and can be account numbers, tokens or slugs.
Get started
Create a second test account today and try opening one of your first account's records with it. One refused request is a good start; one accepted request is your first fix.
whitehatstoic's cybersecurity and AI safety testing includes a web app and API review, a written report with fixes, and a retest after you apply them, scoped after a short call. No test finds every weakness, but two-account testing of every endpoint is a core part of a review.

Building something that has to be safe? Book a meeting with whitehatstoic: tell us the product, the deadline and your biggest worry, and we reply with a plan and a price.
Comments
No comments yet.
Sign in or make an account to comment.