How it works
The explanation, with examples you can run.
The common shapes
Insecure direct object references (IDOR). GET /api/orders/7731 returns order 7731 to anyone logged in. The fix is to make the ownership condition part of the lookup, so the wrong user finds nothing:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Returning 404 rather than 403 for someone else's object also avoids confirming that it exists.
Missing function-level checks. /admin/export-users isn't linked anywhere for normal users, but anyone who finds it in a JavaScript bundle can call it. Every privileged endpoint checks the role on the server, ideally through middleware applied to the whole admin router, so a new route can't forget.
Mass assignment. Frameworks that bind request bodies to models make it easy to save {"name":"Ana","role":"admin"}. Use explicit allow-lists of writable fields or separate input types per action.
Path and method gaps. The check is on GET /items/:id but not on PUT or DELETE, or on /items/:id but not /items/:id/attachments. Check per action, not per URL pattern.
Trusting the client. Prices, user IDs, roles or "isAdmin" flags taken from the request body, a hidden field, or an unsigned cookie.
Server-side request forgery (also in A01 since 2025). The server fetches a URL the user supplies and can reach internal services the user can't. Allow-list destinations, block private and metadata address ranges, and don't follow redirects blindly.
Building it so it holds
- Deny by default, and enforce in one place: middleware or a policy function every handler calls.
- Scope queries by tenant and owner from the authenticated session, not from request fields. In multi-tenant systems, database row-level security adds a second layer.
- Log denials and alert on spikes; a burst of 403s and 404s across sequential IDs is someone scanning.
- Test it: two users, every endpoint, every method. These tests are cheap and catch the bugs scanners miss, because a scanner doesn't know which records belong to whom.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.