Who are you? vs. What are you allowed to do? Getting this distinction wrong is behind most access control bugs.
| Authentication | Authorization | |
|---|---|---|
| Question answered | Who are you? | What can you do? |
| Happens when? | At login / session start | On every request to a protected resource |
| Common mechanisms | Passwords, MFA, OAuth, SSO, certificates | RBAC, ABAC, ACLs, policy engines |
| Failure = ? | Anyone can log in as someone else | Logged-in users access things they shouldn't |
| OWASP category | A07:2021 Identification and Authentication Failures | A01:2021 Broken Access Control |
| Typical bug | Weak passwords, broken MFA, session fixation | IDOR, privilege escalation, missing function-level checks |
| Fix complexity | Well-understood — use established libraries | App-specific — every endpoint needs custom logic |
Authentication establishes who you are. Authorization decides what you may do. They run in that order, they fail in different ways, and conflating them is the root of a large share of real-world access-control bugs — because a system that authenticates beautifully and authorizes carelessly hands every verified user everyone else's data.
They're owned by different code. Authentication is usually centralized — one login flow, one identity provider, one session mechanism — which is why it tends to be reviewed and gets better over time. Authorization is scattered across every endpoint, every query, every new feature, and there is no single place that owns it. That asymmetry is the whole reason broken access control leads the OWASP Top Ten while authentication failures sit lower.
They fail differently under testing. Authentication failures are usually binary and easy to spot: the login bypasses, or it doesn't. Authorization failures are relational — endpoint X, as role Y, against object Z — and the number of combinations grows with the product. You cannot stumble into full authorization coverage; it requires a matrix built deliberately.
They need different evidence. To test authentication you need one account and a lot of creativity about flows: password reset, MFA enrolment, social login linking, session invalidation. To test authorization you need at least two accounts, at different privilege levels or in different tenants, and the discipline to replay one's requests with the other's credentials.
A B2B analytics product uses an identity provider with SAML, enforces MFA, rotates sessions on privilege change, and has never had an authentication finding. The API exposes GET /api/v2/reports/{id}.
A customer's analyst logs in legitimately. Every request carries a valid, MFA-backed session for a real, verified human. They change {id} from 8814 to 8813 and receive another company's report.
Authentication did its job perfectly — the system knows exactly who this is. The handler loads the report by primary key and serializes it, and at no point does anything compare the report's owning organization to the caller's. That single missing comparison is the entire vulnerability, and no amount of authentication hardening touches it.
The fix is not a check added to that handler — it's making the omission structurally impossible: scope the query itself, so the data access layer cannot return a row outside the caller's tenant, and the endpoint has no opportunity to forget.
Authentication rarely breaks at the password prompt. It breaks in the surrounding flows: password reset tokens that are predictable, don't expire, or aren't invalidated on use; host-header injection sending a valid reset link to an attacker's domain; account linking that attaches a social identity by email address without verifying control of it; MFA applied to one login path and not another; sessions established before MFA completes that are already privileged. OAuth and OIDC integration errors belong here too — loose redirect_uri matching, missing state, tokens accepted without checking issuer and audience.
Authorization breaks in two recognizable shapes. Function-level: an administrative operation with no role check, exposed because the UI merely hid the button. Object-level: the operation is permitted but the target isn't yours — the IDOR case, and by far the more common. Inheritance and delegation features are the richest ground, because shared workspaces, team roles and support "act as" tooling exist precisely to grant access across boundaries, which means their code paths were written to say yes.
The approach that finds these reliably is unglamorous: enumerate every role, every object type and every operation, then decide for each cell what the correct answer is before you test it. The bugs live in the cells nobody thought about, which is exactly why an exploratory approach misses them. Burp's Autorize and similar extensions automate the replay half — capture traffic as a high-privilege user, replay as a low-privilege one, flag anything that succeeds — but the matrix itself stays manual.
On the defensive side, the durable pattern for authorization is centralized enforcement at the data access layer with deny-by-default, rather than checks scattered through controllers. For authentication, the durable pattern is to use a well-reviewed library or identity provider and spend your own effort on the flows around it. Full collections: Authentication and Authorization.
Authentication answers "who are you"; authorization answers "what are you allowed to do" — and a system can get the first completely right while getting the second catastrophically wrong.
Authorization, by a wide margin. Authentication is centralized and therefore reviewed; authorization is spread across every endpoint and every new feature, with nothing structurally ensuring it was applied.
Authorization — specifically object-level authorization. The user is correctly authenticated; the system simply fails to check that the requested object belongs to them.
Despite the name, OAuth is a delegation framework — it lets a user grant an application access on their behalf. Your application still has to decide what the resulting identity may do. OAuth scopes are not a substitute for your own access control.
Build a matrix of roles against operations and object types, decide the expected answer for each cell in advance, then replay one account's requests using another account's session. Tools automate the replay; the matrix is the part that finds bugs.