If you run a SaaS product, many customers share the same application, the same database and the same API. The one thing that keeps their data apart is your code checking, on every request, that a user is only touching what belongs to them.
When that check is missing, you have an IDOR.
What IDOR means
IDOR stands for Insecure Direct Object Reference. In API security you may also see it called BOLA, Broken Object Level Authorization. They describe the same problem.
An “object” is anything your application stores: an invoice, a project, a file, a user profile. A “reference” is how the client points to it, usually an ID in a URL or request body:
GET /api/invoices/4512
An IDOR happens when the server fetches invoice 4512 because the client asked for it, without checking that the signed-in user is allowed to see it. Change the number and you see someone else’s invoice.
Why multi-tenant products are especially exposed
In a single-company tool, an IDOR might let one employee see a colleague’s record. That is bad, but contained.
In multi-tenant SaaS, the same flaw lets one customer read another customer’s data. Every customer becomes a potential attacker against every other customer, and all they need is a normal account. Sign-up is often free.
That is why cross-tenant access is usually treated as a critical or high severity issue, and why enterprise customers and auditors ask about tenant isolation directly.
How it usually happens
IDORs rarely come from carelessness. They usually come from reasonable-looking code in the wrong place:
- Authorization in the interface only. The front end only shows a user their own records, so nobody notices the API would return anyone’s.
- Checks on some endpoints but not others. Reading an invoice is protected; updating, exporting or deleting it is not.
- Trusting IDs from the client. The request includes
tenantIdoraccountId, and the server uses it instead of the user’s real session. - New features skipping the pattern. A file export or webhook added later fetches objects directly by ID.
- Nested objects. The project is checked, but the comments, attachments or line items inside it are fetched by their own IDs alone.
Why random IDs are not the fix
Switching from sequential numbers to UUIDs makes IDs much harder to guess, and that is worth doing. But it is not access control.
IDs leak. They appear in URLs, emails, logs, shared links, browser history and API responses. Once someone has an ID, a missing check is just as exploitable as before. Treat unguessable IDs as a second layer, never the only one.
How to prevent it
Scope every query to the current tenant
The most reliable pattern is to never fetch an object by ID alone. Always include the tenant (or owner) from the authenticated session:
SELECT * FROM invoices WHERE id = ? AND tenant_id = ?
If the record does not belong to the caller’s tenant, the query returns nothing, and you return a 404.
Centralise the check
Don’t rely on every developer remembering. Put tenant scoping in a shared layer: a data-access helper, an ORM scope, middleware or database row-level security. Then a new endpoint is protected by default, and skipping the check requires a deliberate choice.
Never trust tenant IDs from the client
Work out the user’s tenant from their session or token on the server. If the client sends a tenantId, ignore it or check it matches.
Test for it automatically
Add tests that sign in as a user in tenant A and request objects from tenant B, for every object type and every method (read, update, delete, export). These tests are cheap to write and stop regressions when new features ship.
Get it tested by people
Automated tests check the cases you thought of. A penetration test checks the ones you didn’t, including indirect paths like exports, webhooks, file downloads and nested objects.
The short version
- IDOR means the server returns or changes an object without checking the user is allowed to.
- In multi-tenant SaaS, it means one customer can reach another’s data.
- Fix it by scoping every query to the caller’s tenant, in one shared place.
- Unguessable IDs help, but are never a substitute for checks.
You can see how we test tenant isolation on our web application and API testing pages.
Want to know whether your tenants are really isolated? Ask us to test it.
