IDOR (insecure direct object reference)
The URL or request body names a record by id and the server hands it over without checking that this user is allowed to have it.
See it
What it is
The request names a record directly (/invoices/1042, or an id in the JSON body) and the server looks it up without asking whether this user is entitled to it. Change 1042 to 1041 and you are reading someone else's invoice. Underneath it is not a clever attack at all, it is a missing if statement, which is why OWASP files it under broken access control and the API list calls it BOLA (broken object level authorization). It is the single most common way real products leak customer data.
The durable fix is to make ownership part of the query rather than a check you have to remember: SELECT ... WHERE id = ? AND org_id = current_org, or push it into the database with row-level security so an unscoped query returns nothing. Centralize it in a policy layer the handlers call, and return 404 rather than 403 for objects the caller cannot see, so you are not confirming that id 1041 exists.
Gotcha: swapping sequential integers for UUIDs is obfuscation, not authorization. It raises the guessing cost and does nothing about ids that leak through shared links, exports, referrer headers, or a list endpoint elsewhere in your own API. Second gotcha: teams fix the read route and forget the neighbors. PATCH, DELETE, the CSV export, the PDF download, the file in object storage, the 'resend webhook' button and the GraphQL node field all take the same id and usually all need the same check.
Ask AI for it
Audit this API for IDOR and fix it. For every route that accepts a record id (path param, query string, request body, GraphQL node id), confirm the handler scopes the lookup to the authenticated user's tenant or ownership, and rewrite any that fetch by id alone so the ownership condition is part of the query itself. Cover the whole family of routes per resource: read, update, delete, list, export, file download, and any admin or webhook replay path. Return 404 instead of 403 for objects outside the caller's scope. Extract the checks into one authorization helper rather than repeating them inline, then add tests where user A requests every one of user B's object ids and asserts a 404 with no data in the body.