The quick-reference authorization checklist
Broken authorization is the number one bug class in APIs, and it has three faces. This is the scannable checklist I run through when I’m testing an API for it — definitions, setup, and copy-paste test steps for each. For the full exploitation depth (every bypass primitive, parser quirks, real CVEs) see the API Security guide; this page is the field checklist that sits next to it.
Authorization bugs don’t show up in a scanner because nothing is malformed — every request is well-formed and authenticated. The flaw is that the server never checked whether this caller should touch that thing. You find them by comparing what one identity can do against what another identity should be able to do, which means the whole game is two accounts and a disciplined replay.
BOLA vs BFLA vs BOPLA
| What it is | OWASP API | One-line test | |
|---|---|---|---|
| BOLA | Access an object you don’t own by changing its ID | API1:2023 | Swap the ID/token; can you read or write someone else’s object? |
| BFLA | Call a function/endpoint above your role | API5:2023 | Hit an admin/privileged route with a low-priv session |
| BOPLA | Read/write properties you shouldn’t on an object you can touch | API3:2023 | Add privileged fields on write; look for hidden fields on read |
BOLA is API-speak for IDOR. BFLA is the vertical version (privilege escalation by function). BOPLA is the field-level version — mass assignment inbound, excessive data exposure outbound.
Setup (do this once, reuse all day)
- Register two accounts, A and B, same role by default.
- Register a third account in a different tenant/org if the API is multi-tenant.
- Register (or get access to) one account per role: anonymous, user, premium, support, admin.
- Log each identity into a separate browser profile or keep their tokens labeled.
- Pull the attack surface: Swagger/OpenAPI, GraphQL introspection, mobile/JS bundles, and proxy traffic. Every endpoint and every object reference is a candidate.
- Install Burp Autorize (or equivalent) with A’s and B’s identities loaded so every request gets auto-replayed with the other session.
BOLA checklist — object level
Capture a request that references an object (/orders/4812, ?userId=99, a UUID in the body) and work the list:
- Replay as another user. Same request, B’s session. Can B read/modify A’s object?
- Swap the identifier, not the session. Increment integer IDs; walk UUIDs you harvested from other endpoints.
- Try
me/current/selfaliases and then substitute a concrete ID (/users/me→/users/1337). - Add the ID where there wasn’t one. Endpoints that infer the object from your token sometimes also accept an explicit
userId/accountIdand trust it. - Client-supplied ID on create. POST a new object carrying an
ownerId/tenantIdthat isn’t yours. - Type/
nullconfusion. Send the ID as a string, array ([99]), or null; parsers that promote or default can skip the ownership check. - Encoding & case. URL-encode, double-encode, change case on hash/UUID IDs; filters that normalize for display but not for authz.
- Reuse an ID across endpoints. An object you can’t reach on
/v2/orders/{id}may be reachable on/v1/orders/{id}, an export route, or a sibling endpoint. - Blind/second-order. The object you touched surfaces later in a notification, PDF, or webhook addressed to the real owner.
BFLA checklist — function level
- List privileged routes from docs/JS (
/admin/*,/internal/*,/users/{id}/roles) and call each with a low-priv token. - Method swap. You can
GETan object — now tryPUT,PATCH,DELETE,POSTon the same path. - Method override.
X-HTTP-Method-Override: DELETE,_method=DELETE, or a tunneling header when the verb is blocked at the edge. - Role in the request. Flip a
role/isAdmin/scopevalue in the body, JWT claims, or a header the gateway trusts. - Guess the admin twin. If
/api/user/exportexists, try/api/admin/export; map the user action to its privileged counterpart. - Path tricks on the gateway. Trailing slash,
;,..;/, case, or double slash to reach a route the edge authz didn’t match but the app serves.
BOPLA checklist — property level
Mass assignment (inbound):
- On any create/update, add fields you weren’t given:
role,isAdmin,verified,balance,tenantId,status. - Copy a privileged object’s full JSON and PUT it back as a low-priv user — see which fields stick.
- Try nested/related writes (
{"user":{"role":"admin"}}) that the serializer binds automatically.
Excessive data exposure (outbound):
- Read an object and inspect every field the API returns, not just what the UI shows — tokens, internal flags, PII, other users’ data riding along in an embedded list.
- Hit list vs detail endpoints: list views often over-return because filtering was assumed to happen client-side.
- Check GraphQL/
?include=/?expand=parameters that pull related objects past your authorization boundary.
Prove impact, then report
A green replay isn’t a finding yet — make it undeniable:
- Show two accounts you control, A’s object ID, and B reading or modifying it. Screenshot both sessions.
- State the concrete impact: PII disclosure, account takeover, cross-tenant read/write, privilege escalation. “Changing the ID returns another user’s record” lands; “missing authz” doesn’t.
- Note scale: are IDs sequential (mass enumeration) or random (targeted)? Sequential BOLA on PII is usually critical.
- Never pivot to real users’ data to demonstrate it — your own two accounts are the proof.
Where to go next
- API Security — the full treatment: every bypass primitive, token attacks, GraphQL, and the rest of the OWASP API Top 10.
- IDOR & Broken Object Access — BOLA is IDOR at the API layer; this goes deep on enumeration and the web-app angle.
- Authorization & Access Control — how to build the object- and function-level checks these tests are probing for.
- Bug Bounty Methodology — where authorization testing fits a full recon-to-report hunt.