Insecure Direct Object Reference
Learn how missing object-level authorization exposes other users' data.
Introduction to IDOR
theoryLearn why object IDs in URLs need authorization checks.
What is an Insecure Direct Object Reference?
Insecure Direct Object Reference (IDOR) happens when an application exposes an internal object identifier and trusts that identifier without checking whether the current user is allowed to access the object.
A common example is a profile route like /users/42. If user 17 can change the URL to /users/42 and read another user’s private profile, the application has an object-level authorization bug.
Why IDOR Happens
IDOR usually appears when code checks that a user is logged in, but does not check whether the requested record belongs to that user.
app.get('/profiles/:userId', requireLogin, (request, response) => {
const profile = db.getUserProfile(request.params.userId);
response.json(profile);
});
The route has authentication, but it does not have authorization for the specific profile object.
Secure Pattern
Load the object, compare its owner with the authenticated user, and reject access when they do not match.
Knowledge Check
What check is missing in a typical IDOR vulnerability?
IDOR Profile Access
simulationPractice finding and fixing a profile endpoint that trusts a URL object ID.
Simulation Objective
Practice finding and fixing a profile endpoint that trusts a URL object ID.
Target
/profiles/:userId
Scenario
Profile lookup
IDOR in APIs and Downloads
theoryLearn where IDOR appears outside profile pages.
Common IDOR Locations
IDOR is not limited to profile pages. Any endpoint that accepts an object ID can become vulnerable if it skips object-level authorization.
Common Places to Check
- Invoice downloads such as
/invoices/1008.pdf - API routes such as
/api/orders/42 - Account settings routes such as
/users/17/settings - File attachments such as
/attachments/771 - Admin actions that accept
user_id,team_id, orproject_id
Vulnerable Example
app.get('/invoices/:invoiceId', requireLogin, (request, response) => {
const invoice = db.getInvoice(request.params.invoiceId);
response.download(invoice.filePath);
});
This route checks that the user is logged in, but it does not check whether the invoice belongs to that user or their organization.
Knowledge Check
Which route is most likely to need an object-level authorization check?
Object Authorization Patterns
theoryCompare ownership checks, role checks, and centralized authorization helpers.
Building Safer Authorization Checks
A good IDOR fix is more than comparing two IDs in one route. The goal is to make object authorization consistent across the application.
Strong Patterns
- Check ownership on the server for every object access.
- Use role or permission checks when teams, projects, or organizations can share objects.
- Query through the current user’s scope when possible.
- Keep authorization helpers centralized and reusable.
Safer Query Pattern
Instead of loading any invoice by ID, scope the lookup to the authenticated user:
const invoice = db.getInvoiceForUser(request.params.invoiceId, request.user.id);
if (!invoice) {
return response.status(404).json({ error: 'Not found' });
}
response.download(invoice.filePath);
This avoids returning objects from outside the user’s allowed scope.
Knowledge Check
Why is querying through the current user's scope safer?
Preventing IDOR
theoryReview defensive patterns for object-level authorization.
How to Prevent IDOR
IDOR is prevented with consistent object-level authorization checks.
Defenses
- Check ownership before returning, updating, or deleting a record.
- Prefer centralized authorization helpers so routes use the same rule.
- Return
404or403when the user cannot access the requested object. - Avoid assuming hidden UI controls or unpredictable IDs are sufficient protection.
Example Fix
app.get('/profiles/:userId', requireLogin, (request, response) => {
const profile = db.getUserProfile(request.params.userId);
if (!profile || profile.userId !== request.user.id) {
return response.status(403).json({ error: 'Forbidden' });
}
response.json(profile);
});
The URL still contains a direct object reference, but the server now verifies that the authenticated user is allowed to access the referenced object.
Knowledge Check
Which defense best prevents IDOR?