How to Fix an IDOR Broken Access Control Vulnerability
An IDOR vulnerability exists when an endpoint checks that the caller is logged in but never checks that the requested record actually belongs to them. Any authenticated user can change the id in the URL and read someone else's data. The fix is a single ownership check before the response.
What makes an endpoint vulnerable to IDOR
Authentication answers "who are you." Authorization answers "are you allowed to do this." An endpoint that only authenticates - but never authorizes on the specific object - is vulnerable to IDOR.
Consider a billing API with a route like GET /invoices/<id>. The endpoint
checks for a valid session, then returns whichever invoice matches the id:
@app.route("/invoices/<int:invoice_id>")
def get_invoice(invoice_id):
user_id = current_user_id()
if user_id is None:
return jsonify({"error": "unauthorized"}), 401
inv = INVOICES.get(invoice_id)
if inv is None:
return jsonify({"error": "not found"}), 404
# BUG: any authenticated user can read any invoice
return jsonify(inv)
An attacker with a valid session for user 1 can request /invoices/1002 and
receive user 2's billing data. Invoice ids are sequential integers - trivial
to enumerate with a loop.
The fix: compare owner_id to the caller's id
After fetching the object, check that the record's owner matches the
authenticated caller. Return 403 Forbidden if it does not:
@app.route("/invoices/<int:invoice_id>")
def get_invoice(invoice_id):
user_id = current_user_id()
if user_id is None:
return jsonify({"error": "unauthorized"}), 401
inv = INVOICES.get(invoice_id)
if inv is None:
return jsonify({"error": "not found"}), 404
# FIXED: ownership check - caller must own this invoice
if inv["owner_id"] != user_id:
return jsonify({"error": "forbidden"}), 403
return jsonify(inv)
The check goes after the 404 guard - confirm the object exists first, then
confirm the caller is allowed to see it. 200 only if both pass.
Why this matters: IDOR is #1 on OWASP
Broken access control is the top entry on the OWASP Top 10. IDOR is its most common form: the object reference is right there in the URL, and without a per-object ownership check, any authenticated user can read, update, or delete anyone else's data just by changing a number.
It is trivially exploitable and commonly missed in code review because the authentication logic is often correct - the bug is the missing second check.
Key patterns and variations
- Use UUIDs or opaque tokens instead of sequential ids to make enumeration harder - but this is defense-in-depth, not a substitute for the ownership check.
- Role-based access - if admins should see all invoices, branch on role
before the ownership check:
if not is_admin(user_id) and inv["owner_id"] != user_id. - Write operations - the same pattern applies to
PUT/DELETEroutes: always fetch the object first, check ownership, then act. - Database-level filtering - in a real app, add
WHERE owner_id = $user_idto the query itself so the DB never returns unauthorized rows; treat the in-code check as an extra safety net.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Reproducing the IDOR by requesting another user's invoice id with your own session
- Adding an ownership check (owner_id != user_id -> 403) before returning the record
- Verifying both the happy path (own invoice -> 200) and the blocked path (other user's invoice -> 403)
FAQ
What is an IDOR vulnerability?
IDOR (Insecure Direct Object Reference) is when an API endpoint uses a user-supplied id to fetch a record but never checks that the record belongs to the caller. Any authenticated user can change the id in the URL and read another user's data.
How do I fix an IDOR bug?
After fetching the object, compare its owner_id (or equivalent) to the authenticated caller's id. Return 403 Forbidden if they don't match. Authentication and authorization are two separate checks - being logged in is not enough.
How is IDOR different from being unauthenticated?
An IDOR vulnerability requires authentication - the attacker has a valid session. The bug is that the server never checks whether this authenticated user is allowed to access this specific record. The session is real; the permission is missing.
Does IDOR come under broken access control?
Yes. Insecure Direct Object Reference (IDOR) is a type of broken access control - it happens when an app uses a user-supplied ID to fetch a record without checking the requester owns it. The fix is a server-side ownership check.
How do you fix broken access control?
Enforce authorization on the server for every request: check the authenticated user owns or may access the specific record, deny by default, and never rely on hidden fields or client-side checks.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →