API security

Five API mistakes I find in almost every assessment

In one paragraph

The five most common API security findings are broken object level authorisation, mass assignment of privileged fields, serialisers returning more data than the client renders, missing rate limits on authentication endpoints, and deprecated or undocumented endpoints left routable. All five are fixable with framework-level patterns rather than new tooling.

After enough assessments the first hour stops being exploratory. There are five things I check immediately, because they are present in some form in most codebases, and any one of them can be the whole report.

1. Broken object level authorisation

The endpoint checks that you are logged in. It does not check that the record belongs to you. Change the ID, get someone else's data. It remains the most common critical finding in API testing and the easiest to prove: two accounts, one request, swapped identifier.

The fix is structural, not per-endpoint. Scope every query to the authenticated principal at the data-access layer — a base query that always filters by owner or tenant — so a developer must actively opt out rather than remember to opt in. Then add a test that asserts a cross-account request returns 404, and make it part of the template for new resources.

2. Mass assignment

A profile update endpoint accepts the whole request body and passes it to the model. The body contains role, is_verified, credit_balance or account_id. The client never sends those fields, so nobody noticed they were accepted.

  • Bind requests to explicit input objects listing the fields you accept.
  • Never pass raw request data into an ORM create or update call.
  • Assert in tests that privileged fields are ignored when submitted.

3. Serialisers that over-fetch

The user list endpoint powers an avatar row, so nobody looked at the response — which contains every user's email address, phone number and internal identifier. The interface renders one field; the API returns forty.

Define response shapes per endpoint rather than serialising models directly. It is more code and it is the only approach that stays correct when someone adds a column.

4. Missing rate limits where it counts

  • Login, OTP request, OTP verification, password reset and email change — all need per-account and per-IP limits.
  • OTP verification without a limit is a four-digit code brute-forced in seconds.
  • Expensive search and export endpoints need limits too, or they become your denial-of-service surface.
  • Enforce at the gateway, so a new service cannot forget.

5. Endpoints nobody remembers

The v1 API left routable "for the old app version". A debug endpoint added during an incident. An internal admin service exposed through the same load balancer. These are never in the documentation, and they are frequently the way in — old code does not get the authorisation improvements the current code received.

  • Maintain an endpoint inventory generated from routing, not written by hand.
  • Version deprecation needs a removal date in a ticket, not a note in a wiki.
  • Internal services belong on a separate network path, not on a path prefix.

The pattern behind all five

Each one is a case of the server trusting something it should verify, or exposing something nobody looked at because the client did not render it. Fix them at the framework level — base query scoping, explicit input and output shapes, gateway limits and a generated route inventory — and they stop recurring in every new feature.

Want this tested properly?

Scope and a fixed quote within two working days. First consultation is free.