Most modern applications are a thin UI wrapped around an API, which means the API is the attack surface — and it's the part users never see, so it's the part that gets tested least. Below is the working checklist I run against REST and GraphQL APIs on every engagement. It maps closely to the OWASP API Security Top 10, but it's organised the way I actually test rather than as a spec to recite.
1. Broken object-level authorization (the number-one API bug)
This is the most common and most damaging API flaw, full stop. The server checks that you're logged in but not that this object is yours. Any endpoint shaped like /api/users/{id} or /api/orders/{id} is a candidate.
How I test it: authenticate as two separate low-privilege users, capture object references belonging to user A, and replay them as user B. If B can read or modify A's objects, that's a broken-object-level-authorization finding — and it scales to a mass data breach the moment the IDs are enumerable. I check reads and writes, and I check nested references (an object ID buried three levels deep in a JSON body is still an access-control decision).
2. Broken function-level authorization
Same idea, one level up: can a normal user reach admin functionality? I look for admin or privileged routes (often just guessable paths like /api/admin/...), and I try to hit them with a non-admin token. I also swap HTTP methods — an endpoint that correctly blocks GET for non-admins sometimes forgets to block DELETE or PUT. Authorization has to be enforced per-method, not per-path.
3. Excessive data exposure / mass assignment
Two sides of the same "the API trusts the shape of data" coin.
- Excessive data exposure: the endpoint returns more than the UI shows. I always read the raw response, not the rendered page — password hashes, tokens, internal flags, and other users' PII hide in fields the front-end simply doesn't display.
- Mass assignment: the endpoint accepts more than it should. I add fields the client was never meant to send —
"role":"admin","isVerified":true,"balance":9999— and see if the server binds them straight onto the model. Frameworks that auto-map request bodies to objects are especially prone to this.
4. Authentication and token handling
- JWT hygiene: is the signature actually verified? I test for the
alg:noneacceptance, weak or default signing secrets, and whether the server honours a token whose claims I've tampered with. I check expiry and whether tokens are invalidated on logout/password-change. - Session and reset flows: are reset tokens single-use, short-lived, and unguessable? Do they ever leak into other responses (they do more often than you'd think)?
- Credential endpoints: rate limiting on login, password reset, and OTP verification. An API with no throttling is a brute-force target by default.
5. Rate limiting and resource consumption
APIs that don't limit request volume enable credential stuffing, enumeration, and denial of wallet (in pay-per-use backends). I check whether limits exist, whether they're per-user or per-IP (per-IP alone is trivially bypassed), and whether expensive operations — search, export, report generation — have their own tighter limits.
6. GraphQL-specific surface
GraphQL concentrates a lot of power behind one endpoint, so it gets its own pass:
- Introspection left enabled in production hands an attacker the entire schema — every type, field and mutation. I check whether it's exposed.
- Deeply nested / recursive queries can exhaust the server; I look for query depth and complexity limits.
- Authorization per resolver: GraphQL makes it easy to protect the top-level query and forget a nested field that quietly returns data the user shouldn't see. Object-level authorization has to hold at every resolver, not just the entry point.
- Batching abuse: aliasing lets an attacker run many operations in one request, which can defeat naive rate limiting on sensitive mutations.
7. Documentation and definition leaks
Swagger/OpenAPI definitions, GraphQL SDL, and Postman collections left exposed give an attacker a complete map of the API — including endpoints the UI never calls. I always look for /swagger, /openapi.json, /api-docs and friends. A leaked spec turns black-box testing into white-box for free. (This is a routine finding, which is why API definition discovery is one of the things I automated into my recon tooling.)
8. Input validation and injection at the API layer
The API is where injection actually lands, since the UI's client-side validation means nothing to a proxy. I test the usual classes — SQL/NoSQL injection, SSRF via any parameter that takes a URL or fetches a resource, and command injection in anything that shells out — directly against the endpoints, not through the front-end that "helpfully" sanitises before sending.
9. Business logic (the part no checklist fully captures)
Beyond the taxonomy above, every API has rules specific to its business — sequences that must happen in order, values that must be re-derived server-side, actions that must happen once. I covered this in depth in Finding Business Logic Flaws Scanners Always Miss; on an API engagement it's where the highest-impact, lowest-noise findings tend to come from.
How the checklist gets used
I run the mechanical parts — object-level auth swaps, definition discovery, header and token checks, injection probes — with tooling so nothing gets skipped, then spend my human time on authorization logic and business rules where reasoning beats automation. That split, "automate the coverage, humanise the judgement," is the whole philosophy behind SentryScan: let the scanner prove the mechanical findings so the tester's attention goes to the ones only a person can see.
If your product is API-first, this is the surface most worth a dedicated look — and the one most likely to be carrying a broken-object-level-authorization bug right now. That's a core service I offer; book a scoping call and we'll map it.