1. Authentication, who is calling
A validated token on every request, checked server-side, with expiry enforced. Two failure modes worth naming: middleware that's applied to some routes and not others, and validation that decodes a token without verifying its signature.
If your framework applies auth via middleware, check what happens on routes that don't match the matcher. Auth gaps at framework level have been the source of real published vulnerabilities, not just theoretical ones.
2. Authorisation. Are they allowed this
This is the layer that goes missing. Across 561 findings in the 24-app study, nothing came close to it for impact.
Authentication tells you someone is logged in. It says nothing about whether they own record 4471. The fix is to make ownership part of the query rather than a check afterwards:
Return 404 rather than 403 so an attacker can't map which IDs exist.
For anything with more than two roles, put this at the data layer, Postgres row-level security or equivalent. Rather than trusting every handler to remember.
3. Rate limiting
Anything that can be brute-forced or costs you money needs a ceiling. Login, password reset, OTP, search, anything calling a paid API.
Limit per user where you can and per IP where you can't, and be stricter on unauthenticated routes. Login endpoints without throttling were one of the most common findings in our study, and they're the cheapest thing in the world to attack.
4. Input validation
Validate at the boundary with a schema, zod, joi, pydantic, whatever fits. And reject anything that doesn't match rather than coercing it.
Allowlists beat blocklists everywhere. Define what's acceptable and refuse the rest; the reverse means enumerating every bad input anyone might send, which never finishes.
Parameterise database queries. Bound anything that takes a size or a count, including pagination limits, or someone will ask for a million rows.
5. Output control
Endpoints frequently return more than the caller should see. SELECT * on a user record sends the password hash, the internal flags and whatever else is in the table.
Serialise explicitly, name the fields you're returning rather than spreading the row. And keep stack traces and database errors out of responses in production; they're a free map of your internals.
The order matters
These are cheap to add and expensive to retrofit, but they're not equally urgent. Missing authorisation leaks data today. Missing rate limiting costs you money and enables credential stuffing. Verbose errors help an attacker who's already looking.
If you're auditing existing endpoints, check authorisation first. It's the one where the code looks correct, which is why it survives review. And why pattern-matching scanners struggle with it. Our RealVuln results bear that out: across 66 labelled repositories, no rule-based scanner cleared 0.19 recall, and authorisation bugs are well represented in the gap.