When OWASP published a Top 10 specifically for APIs, the interesting part was not the list itself but how different it looked from the familiar web application one. Injection had dropped down the ranking. The top entries were dominated by a single theme: authorisation.

That difference reflects a structural reality. Web applications historically rendered pages server-side, and the server decided what a user could see by deciding what to render. APIs invert that. They expose objects and operations directly, the client assembles the experience, and every endpoint becomes an independently reachable entry point that must enforce its own access control.

Broken object level authorisation

The most common and most damaging API flaw is also the simplest. An endpoint accepts an identifier, retrieves that object, and returns it — without checking whether the caller is entitled to it.

GET /api/v1/invoices/10432   -> 200 OK  (your invoice)
GET /api/v1/invoices/10433   -> 200 OK  (somebody else's invoice)

The request is authenticated. The token is valid. The user is who they claim to be. What is missing is the check that this user owns that object.

It is prevalent because it is invisible in normal use. The front end only ever requests identifiers belonging to the current user, so the application appears correct throughout development and testing. The flaw surfaces only when someone changes a number by hand.

Scanners struggle here for a fundamental reason: recognising the flaw requires knowing that invoice 10433 should not be visible to this caller, and that knowledge exists only in the business logic. A tool sees two successful requests.

The defence is to enforce ownership at the data access layer rather than in each controller. Scoping every query by the authenticated principal — so that requesting another user’s object returns nothing by construction — is far more reliable than remembering an authorisation check in every handler. Unpredictable identifiers help marginally but are not access control; they only make enumeration slower.

Broken object property level authorisation

The same failure applied to fields rather than objects, and it runs in both directions.

Outbound, the API returns a complete object and relies on the client to display a subset. The mobile app shows a name and avatar; the JSON also contains the email address, password hash and internal flags. The data is disclosed regardless of what the interface renders, and anyone can read the response.

Inbound, the API binds a request body directly onto a model. The user updates their display name, and the same request sets role: admin because the framework helpfully mapped every supplied field. This is mass assignment, and it converts a profile-edit endpoint into privilege escalation.

Both are solved by explicitness. Define response schemas that serialise only intended fields, and bind input through an allow-list of properties the caller may set. Never rely on the client to filter, and never let a request body decide which fields to write.

Broken function level authorisation

Object-level authorisation asks whether you may access this record. Function-level asks whether you may perform this operation at all.

The failure appears when administrative functionality is protected by not being linked in the interface rather than by a role check. The admin panel calls DELETE /api/v1/users/{id}, the button is hidden for ordinary users, and the endpoint checks authentication but not privilege. Discovering it takes one look at the JavaScript bundle, which ships the entire route table to every client.

Related patterns include older API versions left running without the controls added to the current one, and non-production endpoints exposed in production.

Unrestricted resource consumption

APIs make expensive operations trivially repeatable, and the costs are not only availability. Endpoints that send email or SMS cost real money per call. Ones that invoke a paid third-party service bill per request. Ones that generate reports or process uploads consume disproportionate compute.

Pagination deserves specific attention: an endpoint accepting a client-supplied page size will eventually receive a request for a million records, and the resulting query will affect every other user of that database. Enforce a server-side maximum regardless of what the client asks for.

Rate limiting should be applied per authenticated principal rather than per IP address, since IP-based limits are trivially defeated and simultaneously punish shared corporate egress.

// try it live

API authorisation questions usually start with what the bearer token actually asserts, and how the request is structured.

JWT Decoder JSON Formatter

Inventory, and the endpoints nobody remembers

A recurring theme in API incidents is that the vulnerable endpoint was not on anyone’s list. Version one remained live years after version two shipped. A staging host was reachable from the internet. An acquisition brought infrastructure nobody documented. An internal service was exposed by a routing change.

You cannot secure endpoints you do not know exist, which makes inventory a security control rather than an administrative nicety. Generating documentation from code, so it cannot drift, and periodically enumerating what is actually reachable from outside, tends to reveal more than another scanning tool would.

Retire old versions on a schedule and enforce it. An unmaintained API version is a permanent liability that receives none of the fixes applied to its successor.

Authentication details specific to APIs

Token handling introduces its own failure modes. Tokens must be validated properly — signature verified against an expected algorithm, issuer and audience checked, expiry enforced. Accepting the algorithm declared in the token itself reintroduces well-known forgery attacks.

Long-lived tokens are a persistent weakness, because a leaked token remains useful until it expires and there is often no revocation path. Short lifetimes with refresh, and a genuine revocation mechanism for high-value operations, are worth the complexity.

API keys deserve separating from user authentication conceptually: they identify an application, not a person, and they end up in client-side code, repositories and log files with depressing regularity. Anything relying on an API key alone for authorisation should be treated as public.

Testing for what scanners miss

Because the dominant flaws are authorisation flaws, the highest-value testing is comparative rather than exploratory. Create two accounts, then systematically attempt to access each one’s resources with the other’s credentials. Attempt privileged operations with an unprivileged token. Supply fields the caller should not control. Request more records than the interface would ever ask for.

These checks can be automated in an integration suite, and that is where they belong — as tests that fail the build, run on every change, encoding the authorisation rules the business actually intends. It is unglamorous work, and it catches the class of vulnerability that has caused most large API breaches.

Server-side request forgery, the API variant

APIs frequently accept URLs as input — a webhook target, an image to fetch, a document to import — and each one is a potential server-side request forgery. The service makes a request on the caller’s behalf from a network position the caller does not have, which in cloud environments usually means access to internal services and the instance metadata endpoint.

Blocklist approaches fail reliably here because of the number of ways to express an internal address: alternative IP encodings, DNS names that resolve to internal ranges, redirects from an external host to an internal one, and IPv6 representations. The robust pattern is an allow-list of permitted destinations, resolution of the hostname before the request with validation of the resulting address, refusal to follow redirects, and where possible making outbound fetches from an isolated egress proxy with no access to internal networks or metadata.

GraphQL changes the shape of the problem

GraphQL removes the endpoint-per-resource model that much API security tooling assumes, and introduces its own considerations. Authorisation must be enforced per field and per resolver, because a client can compose a query traversing relationships in ways the designer never anticipated — reaching a user’s orders, then each order’s customer, then that customer’s details.

Query complexity is the second concern. Nested queries can be constructed that expand combinatorially, making a single request extremely expensive. Depth limiting, complexity scoring and pagination enforcement are the standard mitigations, and none are enabled by default.

Introspection deserves a decision rather than a default. It is valuable in development and hands an attacker a complete schema in production; disabling it is not access control, but there is little reason to publish the map.

Logging that supports investigation

API logs are frequently written for debugging and turn out to be useless for security. The properties that matter during an incident are consistent: the authenticated principal on every request, not merely the source address; the operation and the object identifier acted upon; the outcome, including authorisation denials; and correlation identifiers that survive across services so a request can be traced end to end.

Authorisation failures deserve particular attention as a detection source. A single denial is noise; one principal generating denials across many object identifiers in a short window is enumeration, and it is one of the clearest signals available that someone is probing for the object-level flaw described earlier.

Equally important is what must not be logged. Request bodies containing credentials, tokens in URLs, and personal data written to logs create a secondary exposure that frequently outlives the vulnerability that prompted the logging.

Defaults worth adopting

Most API security failures are omissions rather than mistakes, which makes secure defaults unusually effective. A baseline worth enforcing at the framework or gateway level:

Applied centrally, these remove most of the top of the list from individual developers’ hands, which is the only way this holds across a large surface built by many teams.

References

  1. OWASP API Security Top 10 (2023)
  2. OWASP Top 10 — web application risks
  3. OWASP — REST Security Cheat Sheet
  4. OWASP — Authorization Cheat Sheet
  5. RFC 9700 — Best Current Practice for OAuth 2.0 Security
« Threat Modelling with STRIDE: Finding… Container and Kubernetes Security: The… »