Broken Access Control
Stop users from acting outside their intended permissions — enforce authorization on every route and object access.
URL: trusteed.io/academy/web-security/broken-access-control
Broken Access Control happens when an application fails to properly restrict what authenticated users are allowed to do or see — letting them act outside their intended permissions by modifying a URL, changing a hidden form field, forging a request, or simply navigating to a page they were never supposed to reach. It has topped the OWASP Top 10 since the 2021 revision, appearing in the largest share of tested applications of any category.
What Is Broken Access Control?
Access control enforces policy so that users can't act outside their intended permissions. It fails whenever that enforcement happens only in the UI (a hidden button, a client-side route guard) rather than on the server, whenever a check is applied inconsistently across similar endpoints, or whenever a permission decision trusts something the client controls — a URL parameter, a cookie value, a hidden form field — instead of the authenticated server-side session. The category covers a wide range of concrete failures: viewing or editing another user's data by changing an ID in the URL, accessing admin functionality by guessing or discovering its path, and CORS misconfigurations that allow unauthorized cross-origin access to an API.
Why AI Coding Assistants Get This Wrong
Client-side route guards and conditionally rendered UI elements are a natural, common pattern in frontend frameworks — hide the admin button if the user isn't an admin. That's a legitimate UX decision, but it is not access control; it's a display preference, and an assistant generating a frontend component has no inherent reason to also generate the corresponding server-side enforcement unless told the two are separate, both-required layers. The failure mode is subtle because the application "looks" secure in every normal interaction — the vulnerable path only appears when someone bypasses the UI entirely and calls the backend directly.
Vulnerable Pattern
// The frontend hides this, but the backend enforces nothing
app.get('/api/account/:id/statements', authenticate, async (req, res) => {
const statements = await Statement.find({ accountId: req.params.id });
res.json(statements); // no check that req.user actually owns this account
});
// Frontend hides the admin panel link — but doesn't protect the route it points to
{user.role === 'admin' && <Link to="/admin">Admin Panel</Link>}
// /admin itself has no server-side check — reaching it directly bypasses this entirely
Secure Pattern
// Server-side ownership check — enforced regardless of how the request arrives
app.get('/api/account/:id/statements', authenticate, async (req, res) => {
const account = await Account.findOne({ _id: req.params.id, userId: req.user.id });
if (!account) return res.status(404).json({ error: 'Not found' });
const statements = await Statement.find({ accountId: account._id });
res.json(statements);
});
// Server-side middleware protecting the actual route, independent of UI visibility
app.use('/api/admin', authenticate, requireRole('admin'));
Copy-Paste Rules for Cursor, Claude Code, and Codex
---
description: Broken Access Control prevention — server-side enforcement on every request
alwaysApply: true
---
# Access Control Rules
- Every access control decision MUST be enforced server-side. Client-side
route guards, hidden UI elements, and disabled buttons are UX conveniences,
never a substitute for a server-side check.
- Never trust a role, permission, or ownership value read from a URL
parameter, query string, request body, or cookie without cross-checking it
against the authenticated server-side session.
- Apply access control checks by default (deny), and require explicit
configuration to grant access — not the reverse.
- Enforce the same access control policy consistently across every endpoint
serving the same resource, including all HTTP methods (GET, POST, PUT,
PATCH, DELETE) — do not assume protecting one implies the others are covered.
- CORS configuration must use an explicit allow-list of trusted origins.
Never use a wildcard origin combined with credentialed requests.
- Log access control failures (403/401 responses) with enough context to
detect systematic probing, and rate-limit repeated failures from the same
identity or source.
- When generating a new page or route that should be role- or
ownership-restricted, implement the server-side check in the same pass as
the route itself — never defer it to "add auth later."
Verification Checklist
- Every protected endpoint enforces its access control check server-side, independent of frontend behavior
- No permission or ownership decision trusts client-supplied input
- Access control is applied consistently across all HTTP methods for a given resource
- CORS uses an explicit origin allow-list, never a wildcard with credentials
- Automated tests attempt protected actions with a lower-privileged or unauthenticated identity and confirm rejection
- Runtime testing validates access control under real traffic — see Trusteed's WAAP platform for continuous authorization testing
Frequently asked questions
Is hiding a feature in the UI a valid form of access control?
No. Anything reachable over the network is reachable regardless of whether a UI links to it — hiding a button or a menu item is a usability decision, not a security control, and provides zero protection against a request sent directly to the underlying endpoint.
Why does Broken Access Control top the OWASP Top 10?
It's both extremely common and structurally easy to introduce, since it requires a *missing* check rather than a flawed one, and it can hide behind an application that otherwise works correctly for every normal user interaction — testing typically only reveals it when someone deliberately tries to bypass the intended navigation path.
Does using UUIDs instead of sequential IDs solve access control problems?
No. Non-guessable identifiers raise the difficulty of blind enumeration but do nothing to enforce authorization — if an attacker obtains a valid ID through any other means, a missing ownership check is exploited exactly the same way.