Overview
Logging in is only half the story. After the application knows who you are, it must decide what you are allowed to do — and that is access control. A large share of authentication challenges hinge on this second decision: you are correctly authenticated as a normal user, but the application fails to stop you from reaching data or actions meant for someone else. This lesson explains how access control works, the two directions it can be broken, and why it is one of the most common flaw categories in both CTFs and production systems.
Learning objectives
- Distinguish access control (authorization) from authentication.
- Explain horizontal versus vertical access-control failures.
- Recognize common broken-access-control patterns such as IDOR and missing function-level checks.
- Describe the principle that makes access control robust.
Core concept
Access control (authorization) enforces what an authenticated identity is permitted to access. Where authentication establishes who you are, access control decides whether this identity may perform this action on this resource. It fails in two directions:
- Horizontal — a user accesses resources belonging to another user at the same privilege level (reading someone else's order, message, or account). The classic form is IDOR (Insecure Direct Object Reference), where a resource is selected by an identifier the user can change.
- Vertical — a user accesses functionality reserved for a higher privilege level (a normal user reaching admin-only pages or actions). This is often missing function-level access control, where the server shows or hides admin features in the UI but does not actually enforce the restriction on the endpoint.
The core principle that prevents both: every request must be authorized on the server, at the resource, using trusted identity — not on the client, not by hiding links, and not by trusting an identifier the user supplies.
How it works
Broken access control usually comes from checking in the wrong place or not checking at all:
- IDOR / horizontal — An endpoint like
GET /account?id=1004returns the account for the suppliedid. If the server returns whateveridis requested without confirming that the id belongs to the logged-in user, changing1004to1005reveals another user's data. The identity was verified (you are logged in) but the ownership of the resource was never checked. - Missing function-level / vertical — An admin action lives at
POST /admin/delete-user. The application hides the "delete user" button from non-admins, but the endpoint itself does not verify the caller's role. A normal user who simply sends that request performs the admin action, because the only "protection" was a hidden button. - Trusting client-supplied privilege — The server reads a role from a cookie, token claim, or parameter the user can edit (
role=admin) instead of deriving it from trusted server-side state. - Forced browsing — Guessing or discovering unlinked URLs (
/admin,/internal/report) that were "protected" only by not being linked.
In each case the failure is the same shape: the server assumed the client would only ask for things it was supposed to, instead of independently enforcing the rule.
Practical example
In an isolated lab, you log in as a normal user and view your profile at /api/user/1007. The number matches your account. Out of curiosity you request /api/user/1, and the server returns the administrator's profile — an IDOR. The server authenticated you but never checked that the requested record belonged to you, so it happily handed over another user's data, where the flag is stored.
A second challenge shows a vertical failure. The dashboard hides an "Export all users" button unless you are an admin, so it looks protected. But watching the network requests reveals the button calls GET /admin/export. Requesting that URL directly as a normal user succeeds, because the endpoint only relied on the UI to hide itself and never verified your role on the server. Both flags come from the same root cause: authorization was assumed, not enforced. These are controlled exercises on systems you are authorized to test.
Cybersecurity relevance
Broken access control has repeatedly ranked at the very top of the OWASP Top 10 because it is both widespread and high-impact: it leads directly to data exposure, account takeover, and privilege escalation. IDOR and missing function-level checks are among the most reported real-world web findings. The defense is consistent and unglamorous: enforce authorization server-side on every request, check resource ownership for horizontal access (does this object belong to this user?), verify role and permission for vertical access (may this identity call this function?), deny by default, use references that are validated against the session rather than trusted from the client, and never rely on hiding UI elements or unlinked URLs as protection. Practicing IDOR and function-level bypasses in a lab teaches exactly what to test for and what to build.
Common mistakes
- Checking authentication but not authorization — confirming the user is logged in while never checking they may access this resource.
- Enforcing access control only in the UI by hiding buttons or links, while the endpoints stay open.
- Trusting a user-supplied identifier or role instead of validating ownership and privilege against server-side session state.
- Relying on obscurity — assuming an unlinked or hard-to-guess URL is protected.
- Not denying by default, so any endpoint added without an explicit check is unintentionally open.
Key takeaways
- Access control (authorization) decides what an authenticated identity may do; it is distinct from authentication and fails differently.
- Failures are horizontal (reaching another user's resources, e.g. IDOR) or vertical (reaching higher-privilege functionality, e.g. missing function-level checks).
- The root cause is authorization assumed rather than enforced — trusting the client, hiding UI, or skipping ownership/role checks on the server.
- Robust access control enforces authorization server-side on every request, checks ownership and privilege against trusted state, and denies by default.
Check your understanding
- What is the difference between a horizontal and a vertical access-control failure? Give an example of each.
- Why is hiding an admin button in the UI not a valid access-control mechanism?
- In an IDOR, the user is correctly authenticated — so what check is actually missing?

