What Is Broken Access Control?
Access control is the set of rules that decide which authenticated user can read, create, update, or delete which resource. Broken access control happens when the application trusts client-supplied identifiers or hidden UI elements instead of re-verifying permissions on the server for every request. It has topped the OWASP Top 10 since 2021 because it is both extremely common and extremely damaging: a single missed check can expose every record in a database rather than just one field.
Cricket analogy: It is like a scorer who lets any player walk into the dressing room and change the scorecard just because they are wearing team colours, instead of checking that only the official scorer and umpire have write access to the scorebook.
Common Access Control Failures
The most frequent pattern is the Insecure Direct Object Reference (IDOR), where an endpoint like GET /api/invoices/1042 returns whoever's invoice matches ID 1042 without checking that the requesting user owns it; simply incrementing the number leaks other customers' data. Vertical privilege escalation occurs when a regular user reaches admin-only functionality, often because the admin panel is 'hidden' by client-side routing rather than blocked server-side. Horizontal privilege escalation is the IDOR case generalized: same privilege level, wrong record. CORS misconfiguration and forced browsing to unlinked-but-unprotected URLs round out the OWASP-documented failure modes.
Cricket analogy: IDOR is like a scoreboard operator who displays whichever match ID is typed into the console without checking it belongs to the ground currently hosting play, so typing in a rival stadium's match number pulls up their private team data.
// VULNERABLE: trusts the client-supplied ID with no ownership check
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
res.json(invoice); // any logged-in user can read any invoice
});
// FIXED: re-verify ownership (or role) on every request, server-side
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
const invoice = await Invoice.findOne({
_id: req.params.id,
ownerId: req.user.id, // deny by default unless it matches
});
if (!invoice) return res.status(404).json({ error: 'Not found' });
res.json(invoice);
});Hiding a button or route in the frontend is not access control. Any authorization decision made only in JavaScript or client-side routing can be bypassed by calling the API directly with curl or a browser's dev tools. Every privileged action must be re-checked on the server.
Preventing Broken Access Control
The OWASP-recommended posture is deny by default: every route requires an explicit authorization check rather than assuming access unless denied. Centralize the logic in middleware or a policy layer (such as attribute-based or role-based access control) so individual developers cannot forget to add a check on a new endpoint. Log access-control failures and rate-limit repeated denials, since a burst of 403s against sequential IDs is a strong signal of automated IDOR probing. Finally, invalidate stale references: when a user's role changes or an object is transferred, existing sessions and cached permissions must reflect the change immediately, not on next login.
Cricket analogy: Deny-by-default is like the DRS system defaulting to 'not out stays' unless the third umpire actively confirms clear evidence to overturn it, rather than assuming out unless proven otherwise.
Testing tip: for any endpoint that accepts an ID (invoice, order, document, user), log in as User A and try to access User B's resource by swapping the ID. If it succeeds, you've found an IDOR — one of the most commonly reported bugs in bug bounty programs.
- Broken access control is #1 on the OWASP Top 10 (2021) because it is common and high-impact.
- IDOR occurs when an object identifier is trusted without an ownership check.
- Vertical escalation = reaching higher-privilege functions; horizontal escalation = reaching another user's same-level data.
- Client-side hiding (CSS, disabled buttons, hidden routes) is never a substitute for server-side enforcement.
- Adopt deny-by-default: every request must pass an explicit authorization check.
- Centralize authorization logic in shared middleware or a policy engine rather than duplicating checks per route.
- Monitor and rate-limit repeated 403/404 responses as a signal of automated enumeration.
Practice what you learned
1. What is an Insecure Direct Object Reference (IDOR)?
2. Why is hiding an admin button in the frontend insufficient protection?
3. What does 'deny by default' mean in access control design?
4. Which scenario best describes horizontal privilege escalation?
5. What is a strong operational signal of automated IDOR probing?
Was this page helpful?
You May Also Like
Security Misconfiguration
Security misconfiguration covers the gap between a system's secure potential and its insecure default or actual state — from exposed debug consoles to unpatched software and unnecessary open ports.
CSRF Explained and Prevention
Cross-Site Request Forgery tricks a logged-in user's browser into submitting an unwanted, state-changing request to a site it trusts, using the victim's own valid session.
Security Headers Explained
HTTP security headers let a server instruct the browser to enforce protective behaviors, such as blocking mixed content, refusing to be framed, or restricting which scripts may execute, directly at the browser level.