An API can require a valid access token and still expose another customer's order, let a buyer change a server-controlled price, or spend thousands of dollars sending unwanted messages. Authentication answers only one of the questions that determine whether a request is safe to execute.
The OWASP API Security Top 10 provides a practical vocabulary for these failures.
This walkthrough uses the 2023 edition to explain each risk through a bookstore API, connect it to design decisions, and describe what evidence would show that a control works.
OWASP is the Open Worldwide Application Security Project. Its API Security Top 10 is an awareness document about important API risks. It is distinct from OWASP's general web application Top 10, and its category identifiers include an edition year because the list changes between editions.
Treat the categories as prompts for investigating your API, rather than a complete security specification or a universal order for fixing issues. An exposed administrative operation may deserve attention before a lower-impact object access issue, regardless of their positions in the list. Injection and missing security monitoring still matter even though they do not have separate categories in the 2023 API list.
Our fictional API runs over HTTPS at api.bookstore.example. It serves multiple bookstores, which this chapter calls tenants. Customers can read their own orders and manage their profiles; store operators have explicitly assigned administrative permissions. A verified identity and server-side membership checks establish the active tenant. A caller-supplied tenant value alone grants nothing. Tokens these examples show below are nonfunctional placeholders.
The following map introduces the ten categories. The examples and controls that follow are application design choices, not rules that OWASP or HTTP impose on every bookstore.
Broken Object Level Authorization, or BOLA, occurs when the caller can use an operation but lacks permission for the particular object it accesses. An object might be an order, invoice, file, or cart.
Suppose customer Alice owns order_901. Charlie owns order_902 in the same bookstore. Alice sends:
An intentionally flawed handler verifies Alice's token, loads the order by ID, and returns it. Authentication succeeded, but no check connected Alice to this order. A tenant-only filter would also fail here because both customers belong to the same bookstore.
A corrected lookup applies the relevant access policy before returning data. For this customer-only endpoint, a simplified database query could be:
The identity values come from verified server context. This ownership rule is specific to customer order access; support access or shared accounts would require their own explicit policy.
This API chooses to conceal inaccessible orders with the same response it uses for missing orders:
HTTP permits using 404 to hide a forbidden resource's existence. That choice does not replace authorization, and the error must not include the real owner's identity. An authorized request for order_901 returns a deliberately limited representation:
Apply equivalent checks to writes, exports, nested resources, and every item in a batch. Random identifiers reduce guessing but do not grant permission. Verification should cover another customer's order in the same tenant, an order in another tenant, a missing order, and an allowed order. A denial must leave stored state unchanged.
Broken authentication lets an attacker make the API accept an invalid identity or take over a legitimate one. It can affect token verification, login, credential recovery, and changes to account recovery details.
An intentionally flawed implementation decodes a signed token and trusts its customer identifier without verifying the signature. A corrected implementation uses a maintained verification library with trusted keys and an explicit token acceptance policy. For signed JWT access tokens, that policy includes permitted algorithms, the expected issuer and audience, and time validity. Merely parsing the token is insufficient.
For example, an expired bearer token receives:
Token checks do not prevent credential stuffing, where attackers try username and password pairs they stole from other services. Protect login and recovery flows against repeated attempts, and use stronger identity checks for sensitive account changes. Recovery tokens should expire and become unusable after successful redemption.
Verify rejection of expired, tampered, and wrong-audience tokens. Also verify that a used recovery token cannot work again. These checks address distinct routes to impersonation.
Permission to access an object does not imply permission to access all its fields. This category covers both exposing fields the caller must not read and accepting changes to fields the caller must not write.
Consider a profile update contract that allows customers to change only display_name. The following request intentionally includes a forbidden property:
A flawed handler copies every submitted field into the database model. This is mass assignment: applying incoming properties broadly without checking which ones the operation permits.
The corrected contract rejects the entire update and changes neither field:
Here, 400 is the API's chosen response for fields outside its update contract. A schema can define the accepted fields and their types, but role-dependent field permissions also need authorization logic. Separate input models from storage models, and map accepted values explicitly.
For reads, construct an explicit response representation. Returning an entire customer record can expose internal fraud notes even if the browser never displays them. Nested objects and optional field selectors require the same care.
Verify that adding a forbidden field cannot modify it, that rejection is atomic, and that customer responses omit internal fields. Repeating these checks when storage models gain new properties catches accidental exposure through automatic serialization.
A request can consume CPU, memory, storage, bandwidth, or paid external services. This risk occurs when the caller can drive that consumption beyond acceptable bounds.
Suppose an authorized operator can request an order export. One request spanning years of data may be more expensive than thousands of small reads. Moving it to a queue changes where the work happens; it does not bound the work.
For our bookstore, an illustrative export policy permits at most a 31-day range and two active exports per tenant. These values require workload testing and product agreement. Enforce the range before scheduling work, reserve an active-job slot atomically, and release it when the job finishes or the service cancels it. Bound execution time and output size inside the worker as well.
An operator who submits many simultaneous requests must not create more than two active jobs because each request observed an empty queue. Count actual downstream work, including batch items and paid message sends, rather than only HTTP requests.
Verification should check oversized requests, concurrent submissions, and worker cancellation. Measure jobs the service created, bytes it processed, and provider calls. A quick rejection is useful only if expensive work has not already started.
Broken Function Level Authorization occurs when a caller can execute an operation that its permissions should prohibit. For example, a customer who can request a return should not thereby gain permission to issue a refund.
Assume this bookstore exposes a refund-issuance operation that only authorized store operators may use. A customer attempts:
The corrected service denies the operation before creating a refund or calling a payment provider:
Hiding the button or putting the route under /admin cannot enforce this policy. Check the required permission on the server, deny access when no rule grants it, and apply the policy to every route that reaches the operation.
The three authorization categories protect different boundaries. This diagram shows their relationship for a refund operation after authentication:
The checks are conceptually separate even if code combines them. An operator who may issue refunds still needs access to the particular order and permission for the submitted fields. Business rules, such as the maximum refundable amount, remain necessary after authorization succeeds.
Verify the permission matrix using a customer, an allowed operator, and an operator from another tenant. Confirm that alternative routes and methods do not bypass the same policy.
Some requests are individually valid but harmful when callers automate them. This category concerns access to business workflows whose repeated use can undermine the product even when authorization works and infrastructure stays healthy.
Suppose the bookstore holds a signed copy for ten minutes when a buyer starts checkout. Automated buyers repeatedly reserve the available copies and abandon checkout. Each request may be inexpensive, and each account may stay below a request-rate limit, while ordinary customers see no stock.
The failure is easier to see when you examine technical capacity and business outcomes separately:
A healthy latency graph does not establish that inventory remains available fairly. For this workflow, controls might combine limits on active reservations, rules for repeated abandonment, short hold expiry, and stronger verification for scarce releases. Define what legitimate buyers need before adding friction, and account for automation across multiple accounts.
API4 focuses on consumption of technical resources and spending. API6 focuses on harmful use of a business capability. A campaign can cause both.
Verify the workflow across repeated reservations and expiry, including concurrent attempts. Observe completed purchases and inventory availability as well as HTTP errors. An idempotency key can suppress a retry, but new requests with new keys can still abuse the workflow.
Server Side Request Forgery, or SSRF, occurs when caller-controlled input causes a server to make requests to destinations it should not contact. The server's network access may reach systems that the caller cannot access directly.
Imagine a cover-import operation that accepts source_url. A flawed implementation fetches any supplied URL from an application server with access to internal services. Authorization to import a cover does not authorize using that network access.
Prefer an upload or an approved provider identifier when arbitrary URLs are unnecessary. If the feature needs remote fetching, constrain schemes, destinations, and ports using a well-tested URL parser and fetching component. An allowlist is an explicit set of permitted destinations; checking whether a string merely contains an approved hostname is insufficient.
The fetch boundary should remain distinct from normal application access:
An outbound network policy, or egress policy, limits where the worker can connect. It complements application checks. Account for DNS resolution at connection time and address changes between checking a hostname and connecting. Disable redirects unless the feature needs them; if you enable them, validate each destination. Deny loopback, private, link-local, and other prohibited address ranges across IPv4 and IPv6 according to the deployment policy.
Bound response size and fetch time, and avoid forwarding caller credentials. Not returning the fetched content is insufficient: an unwanted outbound request can itself cause harm.
In a controlled test environment, verify that the fetcher blocks prohibited destinations and redirects before connecting. Include a permitted provider fetch so the test proves the feature remains usable.
The surrounding configuration can undermine correct handler code. This includes application settings, reverse proxies, cloud permissions, transport settings, and deployed software versions.
Suppose the public gateway verifies credentials, but the application origin remains directly reachable and trusts identity headers from any source. A caller can bypass the gateway. The intended trust boundary exists in the architecture diagram but not in the deployment.
Make the origin reachable only through intended paths, and ensure it trusts identity assertions only from authenticated infrastructure. Remove or overwrite caller-supplied identity headers at that boundary. Another design is for the application to verify credentials itself; the appropriate choice depends on the deployment.
Use repeatable production configuration, disable debug interfaces, restrict administrative surfaces, and apply security updates. Errors should not expose stack traces or secrets. Browser access policies such as CORS do not authorize requests from arbitrary HTTP clients.
Verification must inspect the deployed system. Test direct origin access from an untrusted network, check debug routes, and compare live settings with the approved configuration. A source-code review alone cannot prove which listeners, routes, or permissions are active.
An API inventory records the deployed hosts, versions, operations, owners, and relevant data flows. Inventory management fails when the organization loses track of what exists, who maintains it, or how to retire it.
Suppose /v2/orders enforces customer ownership, while a forgotten mobile /v1/orders deployment still reaches the production database without that check. Securing v2 leaves the same order data exposed through v1.
Documenting an endpoint as deprecated does not disable it. Reconcile the inventory with deployed routes and observed traffic, identify remaining consumers, and remove retired access paths. Record third-party data sharing too; an unused integration can retain unnecessary access to customer data.
For this bookstore, a useful inventory entry includes the hostname, version, owner, environment, exposure, authentication policy, data the API accesses, known consumers, and retirement status. These fields turn “we have an old API” into an actionable ownership and migration decision.
Verification should establish that a retired version is unreachable through all known routes and that the team has removed obsolete access. Where an old version must remain active, maintain its protections explicitly. A staging hostname that can access production data belongs in this review even if no public documentation mentions it.
An API also acts as a client when it calls payment, shipping, catalog, or identity services. Unsafe consumption occurs when it gives incoming data from those integrations more trust than the data deserves.
Suppose a shipping provider returns a quote for an order. An intentionally flawed integration accepts any successful HTTP response and copies its body into the order's shipping fields. A successful status does not prove that the body is valid for this order.
The bookstore expects a bounded, documented quote representation:
The integration checks structure and types, rejects a negative amount, verifies the currency, and matches the quote to the requested order. It maps accepted properties explicitly instead of letting extra provider fields overwrite local order state. Even correctly typed data can violate these business expectations.
Authenticate the provider connection, verify TLS certificates, and bound response size and processing time. Treat returned strings as data when storing or rendering them. Review redirects and any secondary URLs before following them.
API7 asks whether the server should contact a destination. API10 asks how the server handles an API interaction and the data it receives. An approved destination still needs response validation.
Verify malformed, oversized, mismatched, and delayed provider responses with a test double. For an invalid quote, leave checkout pending or return a controlled failure according to the product contract; silently treating the quote as free shipping would introduce a new business error.
The categories become useful when you connect them to a concrete operation and an observable outcome. For the refund operation, a review might produce the following acceptance conditions:
Status codes are only part of that evidence. Inspect state changes, queued work, and external calls, because an API can return a denial after accidentally performing an action. Include successful authorized cases so that a control does not pass simply by rejecting everything.
Record security-relevant outcomes with enough context to investigate failures, while excluding credentials and unnecessary personal data. Automated checks help catch regressions, but business abuse and forgotten deployments also require operational and product knowledge. Prioritize findings using exposure, impact, and the controls that actually exist.
The OWASP API Security Top 10 separates risks that are easy to blur together. Establishing identity, permitting an operation, allowing access to a record, and exposing or changing its fields are distinct decisions. Resource budgets and business-flow protections address different forms of excessive use. Security checks must also cover outbound requests, provider responses, deployment configuration, and forgotten API versions, beyond the request handler itself.
Use each category to identify a concrete failure, define the control at the correct boundary, and verify both the response and the resulting effects. The list provides a starting point for that work; the API's data, workflows, and deployment determine the actual risks.