API Security Testing

API security testing checks that an API refuses what it must refuse and returns only what the caller is entitled to when it is called directly, not through the app's screens. An API answers curl as readily as it answers the mobile app, so every rule the screen seems to enforce has to be enforced again by the server. A typical finding: the app shows a customer only their own invoices, but GET /v1/invoices/88213 with the number changed returns another customer's invoice.

The test basis for this lesson is the OWASP API Security Top 10 (2023), an awareness list of ten API security risks that the OWASP project team ranked with the OWASP Risk Rating Methodology: how easy each weakness is to exploit, how widespread and how easy to detect it is, and its technical impact. How widespread a weakness is was agreed by the team, not measured from collected data, so the list ranks risks rather than counting incidents. You will learn what each risk is and one safe check for it, then go deeper on four: broken object level authorization (BOLA), broken authentication, excessive data exposure and missing rate limits.

Why it matters

The API carries the data; the user interface only displays it. Hidden buttons and client-side validation protect nothing against a caller who talks to the API directly, and most callers do: mobile apps, partner integrations and other services. One missing ownership check exposes every customer's records to every other customer; one unlimited one-time password (OTP) endpoint lets a script try codes until one works. Defects like these turn into data breaches and fraud, not cosmetic bugs.

For the tester the good news is that most of these checks are variations of what you already do: change the token, change the id, read the raw response, repeat the request, compare two responses. They need no scanner and no exploit skills, and a functional tester who runs them for every new endpoint finds many of these defects long before a penetration test does.

The OWASP API Security Top 10 (2023) as a checklist

OWASP publishes a separate Top 10 for APIs because API risks differ from web-page risks: there is no browser to attack, but there are ids in every request, tokens instead of sessions and no screen to hide anything. The 2023 edition replaced the 2019 one and reorganised the list; two 2019 names are still used by many teams, "Excessive Data Exposure" (now part of API3) and "Lack of Resources & Rate Limiting" (now API4), so this lesson gives both names.

Read each entry as a question to ask about every endpoint, with a safe check you can run with your own test accounts in a test environment:

  • API1:2023 Broken Object Level Authorization (BOLA): the API trusts an id from the client without checking that the caller may access that object. Check: the same request with another user's token.
  • API2:2023 Broken Authentication: tokens and logins are weak or unlimited: tokens accepted after expiry or logout, unlimited password or OTP attempts, credentials in URLs. Check: missing, expired, revoked and tampered tokens; count the attempts allowed.
  • API3:2023 Broken Object Property Level Authorization: the response carries properties the caller must not see (excessive data exposure) or the request may set properties it must not (mass assignment). Check: read the raw JSON against the contract; send an extra field.
  • API4:2023 Unrestricted Resource Consumption: no limit on request rate, page size, upload size or operations that cost money such as SMS. Check: repeat quickly, ask for pageSize=50000, upload one byte over the maximum.
  • API5:2023 Broken Function Level Authorization: a role can call another role's function. Check: a customer token on an admin endpoint, as in the lesson on API authorization testing.
  • API6:2023 Unrestricted Access to Sensitive Business Flows: a flow that harms the business when automated (buying all stock, booking every slot, mass sign-ups) has no limit, no step-up check (an extra verification such as a CAPTCHA or a one-time code) and no review. Check: script the flow with one account and see whether anything slows it down.
  • API7:2023 Server Side Request Forgery (SSRF): the server fetches a URL supplied by the client, such as a webhook or an image import, without an allow-list. Check: supply a capture URL you control (one that records every request it receives, such as a Webhook Tester URL) and confirm what the server fetches; ask the developer how private addresses are refused rather than probing them.
  • API8:2023 Security Misconfiguration: verbose errors with stack traces, HTTP without TLS, CORS that allows any origin, unneeded methods, default settings. Check: one malformed request and the response headers.
  • API9:2023 Improper Inventory Management: old versions and debug endpoints still answer; the OpenAPI document and the deployed API differ. Check: call /v1 after /v2 shipped; list what answers but is not documented.
  • API10:2023 Unsafe Consumption of APIs: data from a partner API is trusted without validation, size limits or redirect checks. Check: mock the partner and return bad data.
Each OWASP API Security Top 10 (2023) entry becomes one question about every endpoint and one safe check with your own test accounts; the shaded rows are the four risks this lesson teaches in depth.
OWASP API entryWhat goes wrongOne safe checkExpected
API1:2023Broken Object Level Authorization (BOLA)An id from the client is trusted without checking that the caller may access that objectThe same request with another user's token, same role404 or 403; no data of the other user
API2:2023Broken AuthenticationTokens accepted after expiry or logout; unlimited password or OTP attemptsNo token, expired, revoked and tampered token; count the wrong OTP attempts401 with WWW-Authenticate and no data in the body; 429 or a lockout after a few attempts
API3:2023Broken Object Property Level AuthorizationThe response carries properties the caller must not see (excessive data exposure); the request may set properties it must not (mass assignment)Compare the raw JSON with the contract; add "role": "admin" to a valid PATCH, then read the object backOnly the fields in the contract; 400, or the field ignored and unchanged
API4:2023Unrestricted Resource ConsumptionNo limit on request rate, page size, upload size or operations that cost moneyRepeat quickly with agreed numbers; ask for pageSize=50000; upload one byte over the maximum429, with Retry-After if the contract has it; a capped page; 400 or 413
API5:2023Broken Function Level Authorization (BFLA)A role can call another role's functionA customer token on an admin endpoint, from the users × requests matrix of the API authorization testing lesson403 or 404; nothing changed
API6:2023Unrestricted Access to Sensitive Business FlowsA flow that harms the business when automated has no limit, step-up or reviewScript the flow with one accountSomething slows or stops it: a limit, a step-up, a review
API7:2023Server Side Request Forgery (SSRF)The server fetches a URL supplied by the client without an allow-listSupply a capture URL you control; ask the developer how private addresses are refusedOnly allow-listed hosts are fetched
API8:2023Security MisconfigurationStack traces in errors, HTTP without TLS, CORS for any origin, unneeded methods, default settingsOne malformed request; read the response headersA generic error without internals; headers as agreed with the team
API9:2023Improper Inventory ManagementOld versions and debug endpoints still answer; the OpenAPI document and the deployed API differCall /v1 after /v2 shipped; list what answers but is not documentedRetired versions refuse; documentation and deployment match
API10:2023Unsafe Consumption of APIsData from a partner API is trusted without validation, size limits or redirect checksMock the partner and return bad dataValidated and rejected; no crash, nothing stored
  • Taught in depth in this lesson

Every check: your own test accounts, a test environment and written permission; stop at the first record that proves a defect.

Try it: OpenAPI → Test Cases Try it: Webhook Tester Try it: API Header Validator

BOLA and broken authentication: change the token, then the id

BOLA is the top risk because ids are everywhere in APIs and the check that the caller owns the object must be written for every one of them. The lesson on API authorization testing built the users × requests matrix; a security pass extends it to the ids the matrix usually misses: query strings such as ?accountId=, request bodies, headers, nested routes such as /parcels/7731/label.pdf, batch and export endpoints, and file downloads, which are often served by a different code path with no check at all. Always use two accounts of the same role, and stop at the first record that proves the defect: your job is to show that the check is missing, not to collect other people's data.

Broken authentication in an API is mostly about tokens and the endpoints around login. For every protected endpoint send the request four ways and expect 401 each time, with a WWW-Authenticate header as in the lesson on API authentication and no data or internal detail in the body: no token, an expired token, a token from a session that has logged out, and a token with one character of the signature changed. Then look at the endpoints that issue credentials:

  • Password, OTP and password-reset verification: count the wrong attempts the API accepts before a 429 or a lockout. A six-digit OTP has one million possible values; with no limit, guessing is a matter of time.
  • JWTs: the header must not be accepted with "alg": "none", and a token issued for another audience, or by an issuer the API does not trust, must be refused; RFC 8725 (JWT Best Current Practices) sets out these checks. The JWT Header Analyzer and JWT Claims Analyzer show what a token claims before you test what the API does with it.
  • Error bodies: "wrong password" and "no such account" must read the same, or the login endpoint confirms which accounts exist.
  • Credentials and tokens never belong in URLs, logs or error messages; a token in a query string ends up in every proxy log.
# One protected request, four ways.
# Expected: 401 each time, with WWW-Authenticate and no data in the body
GET /v1/users/me HTTP/1.1
Host: api.example.test
# 1. no Authorization header
# 2. Authorization: Bearer <expired token>
# 3. Authorization: Bearer <token of a session that has logged out>
# 4. Authorization: Bearer <valid token with one signature character changed>

# OTP verification: send code 000001, then 000002, 000003 ...
# Expected: 429 or a lockout after a small number of attempts
POST /v1/auth/otp/verify HTTP/1.1
Host: api.example.test
Content-Type: application/json

{"phone": "+9665XXXXXXXX", "code": "000001"}

Try it: API Authentication Tester Try it: JWT Header Analyzer Try it: JWT Claims Analyzer

Excessive data exposure and missing rate limits

Excessive data exposure happens when the API returns the whole object and relies on the client to show only part of it. The profile screen shows a name and an avatar; the raw response also carries the e-mail address, the phone number, the national id, internal notes or a password hash. The 2023 edition folds this risk together with mass assignment into API3, because both are the same missing question: may this caller see, or set, this property? Test the read side by comparing every raw response with the contract, as in the lesson on API schema validation, and asking for each field whether this role needs it. Test the write side by adding a property the caller must not control, "role": "admin", "balance": 9999 or "verified": true, to an otherwise valid PATCH or POST, then reading the object back: the correct outcomes are 400, or the field silently ignored and unchanged.

Rate limits protect the resources every request consumes: CPU, memory, bandwidth, database connections and money, when an endpoint sends an SMS or calls a paid service. Without them one client can exhaust the service for everyone or run the guessing attacks above. RFC 6585 defines 429 Too Many Requests for this and says the response may carry a Retry-After header. A safe test has three parts and one place:

  • Rate: send the same request quickly, with numbers agreed with the team, and record the request number at which the first 429 appears, the headers it carries, and whether the limit is per user, per API key or per IP address, since a limit only per IP restarts when the client changes address.
  • Size: request ?pageSize=50000, send a body or upload one byte over the documented maximum, and nest a JSON body deeper than any client would. Expect 400 or 413 and a capped page, never a 50000-row response.
  • Cost: for endpoints that send SMS or e-mail or call a partner, confirm that a per-user daily limit exists and is enforced by the server, not by the app.
  • Place: only a test environment you are permitted to load, with numbers agreed with the team, because the test itself is load; never production and never anyone else's system.

Try it: API Response Analyzer Try it: API Schema Validator Try it: API Request Builder

Worked example: a security pass on a telecom self-service API

A telecom self-service API in the test environment, two customer accounts A and B with their own test lines, an OpenAPI document and written permission from the team. The plan covers five endpoints and the four risks above; expected results were written from the contract before the first request. Six of the eight checks found defects:

  • C1, BOLA: B's token on GET /v1/accounts/A/invoices returned 200 with A's invoices instead of 404. Critical: any customer can read any customer's invoices.
  • C3, broken authentication: a token from a session that had logged out was still accepted; logout only deleted it on the device. Major.
  • C4, broken authentication: 50 wrong OTP codes in a row, all 400, no 429, no lockout. Critical: the login code can be guessed.
  • C5, excessive data exposure: GET /v1/users/me returned nationalId and passwordHash, neither of which is in the contract. Critical: identity data and a password hash reach any client.
  • C6, mass assignment: PATCH /v1/users/me with "planTier": "premium" returned 200, and a fresh GET showed the plan upgraded. Major, with a financial impact.
  • C7, resource consumption: ?pageSize=50000 on the usage records of A's test line returned all 12000 records in one page, in nine seconds; the limit in the contract (100) was not enforced. Major.
Telecom self-service API, test environment. Customers A and B, each with a test line.

#   Risk  Request (actor)                               Expected     Observed
C1  API1  B: GET /v1/accounts/A/invoices                404          200, A's invoices
C2  API2  no token: GET /v1/users/me                    401          401
C3  API2  A after logout: GET /v1/users/me              401          200
C4  API2  POST /v1/auth/otp/verify, 50 wrong codes      429 early    50 x 400, no lockout
C5  API3  A: GET /v1/users/me, fields vs contract       contract     2 extra fields
C6  API3  A: PATCH /v1/users/me {"planTier":"premium"}  400/ignored  200, plan upgraded
C7  API4  A: GET /v1/accounts/A/usage?pageSize=50000    100 rows     12000 rows, 9 s
C8  API4  A: GET /v1/accounts/A/invoices, 60 in 10 s    429 after 30 429 from request 31

Try it: REST API Tester Try it: API Response Comparator

Common mistakes

  • Testing only through the app's screens, so you never see the raw response and never send a request the app would not send.
  • Using one account per role, which makes object-level tests impossible, or using real customer accounts and data.
  • Judging by the status code alone: a 403 whose body still contains the object is a leak, and a 200 does not tell you whether a forbidden field was saved; read the body and the stored state too.
  • Treating random UUIDs or undocumented endpoints as protection: ids leak through links, logs and other responses, and every endpoint is visible in the app's traffic.
  • Running rate-limit and size tests against production or a shared environment, or without agreeing the numbers with the team.
  • Stopping at "the scanner found nothing": a scanner does not know your access policy, so BOLA and property-level defects are found only by tests written from it.
  • Reporting a security defect with real tokens or personal data in the ticket, or without the OWASP category and a severity.

The QA perspective

Plan the security rows while you design the functional API tests for a new or changed endpoint, with the OpenAPI document as the test basis, and keep them in the same data-driven suite so they run in the pipeline. Ask the developer which fields each role needs, where the rate limit is enforced and what its numbers are, which versions are still deployed and what happens to a token after logout; ask the product owner which business flows must not be automated; and hand anything beyond this scope, such as exploitation or infrastructure, to the security team or a penetration tester.

Record for every check the endpoint, the actor and the owner of the object, the request, the expected and observed result, the state check and the OWASP API entry, with tokens and personal data masked, and file security defects in a restricted ticket, not on a public board. Test only systems you own or have written permission to test, and stop at the first record that proves a defect: a tester shows that a check is missing; extracting other people's data to prove the point is neither needed nor allowed.

Practice exercise

Plan a security pass for a parcel-delivery API

Your task

A parcel-delivery API in the test environment. Customers create parcels and may change only the delivery notes of their own parcels; couriers see the parcels assigned to them; the contract hides parcels the caller may not see with 404, caps pageSize at 50 and allows ten OTP attempts per phone number per hour. You have customer accounts A and B, courier account C and written permission. Parcel 7731 was sent by A and is assigned to C.

Plan one safe check per risk for the five endpoints below, then judge the three observed results.

Material
Parcel API, test environment. Parcel 7731: sender A, courier C.

E1  GET    /v1/parcels/{id}            details: address, phone, items, price
E2  GET    /v1/parcels/{id}/label.pdf  shipping label download
E3  POST   /v1/auth/otp/verify         {"phone": "...", "code": "123456"}
E4  PATCH  /v1/parcels/{id}            {"notes": "..."}
E5  GET    /v1/parcels?pageSize=50     the caller's parcels

Observed:
O1  B:  GET /v1/parcels/7731                             -> 404
O2  A:  PATCH /v1/parcels/7731                           -> 200, status DELIVERED
        {"notes": "ring the bell", "status": "DELIVERED"}
O3  POST /v1/auth/otp/verify, 30 wrong codes in 20 s     -> 30 x 400, then 429
Steps
  1. For each endpoint E1 to E5, name the OWASP API Security Top 10 entries to check; several may apply.
  2. Write one safe check per entry: actor, request, expected result.
  3. Judge O1 to O3: correct or defect, with the OWASP entry and a severity.
  4. Name one check you must not run in this environment without agreement, and why.
Show the model answer
Model answer

E1: API1 (B's token → 404; C's token → 200; C on a parcel not assigned to C → 404) and API3 (does a courier need price? compare the raw fields with what each role needs). E2: API1 again, because downloads often skip the check, and API8 if the label URL works without a token. E3: API2 and API4 (the same generic body for a wrong code and an unknown phone; a 429 by the eleventh attempt). E4: API3 (an extra "status" must be rejected or ignored and unchanged on re-read) and API1 (B on 7731 → 404, parcel unchanged). E5: API4 (pageSize=50000 → capped at 50 or 400) and API1 (the list for B must not contain 7731, even with ?senderId=A added).

O1 is correct. O2 is a defect: mass assignment, API3, a customer marked their own parcel delivered; major, and critical if delivery status drives payment to couriers. O3 is a defect: the contract allows ten attempts per phone per hour and the API allowed thirty; API2 and API4, major; also ask whether the limit is per phone number or per IP address, since a limit only per IP restarts when the client changes address.

Not without agreement: the rate and size tests on E3 and E5 at scale, because they load a shared environment; agree the numbers and the time window with the team, and never run them against production.

Frequently asked questions

Is this the same as a penetration test?

No. A penetration test is done by specialists with permission to exploit, chain findings and cover infrastructure and often production. The checks here are the safe part a functional tester runs for every endpoint from the access policy and the contract, so that the penetration test is not the first time anyone changes an id.

Do I need a scanner to test API security?

Scanners find misconfiguration, missing headers and known patterns, and the next lesson shows where they fit. They cannot know that order 9021 belongs to customer A or that a courier must not see prices, so BOLA, function-level and property-level defects are found only by tests written from the policy, which is what this lesson teaches.

Our documentation still says "Excessive Data Exposure" and "Lack of Resources & Rate Limiting". Are those wrong?

They are the 2019 names. In the 2023 edition excessive data exposure and mass assignment became API3:2023 Broken Object Property Level Authorization, and lack of resources and rate limiting became API4:2023 Unrestricted Resource Consumption. Use the current ids in reports and add the old name in brackets while the team gets used to it.

What should an API return when a limit is hit?

RFC 6585 defines 429 Too Many Requests and says the response should explain the condition and may include a Retry-After header. A 200 that silently returns fewer rows than asked is acceptable for a page-size cap when the contract says so; a 500 or a timeout is a defect: the limit is missing or does not hold.

References

Practise with Mutqan tools

Test Bearer, Basic, API-key and custom-header authentication against an endpoint and compare authorised vs unauthorised responses.

API Testing

Inspect the JOSE header of a JWT: alg, typ, kid, jku, x5u and potential algorithm-confusion risks.

Security

Analyze JWT claims for missing, risky or non-standard values and get security recommendations.

Security

Analyze an API response: structure, field types, nulls, duplicates, sizes and potential data-quality issues.

API Testing

Test REST endpoints with full control over method, headers and JSON body, then assert on status, headers and JSON paths.

API Testing

Generate a structured test-case matrix (positive, negative, auth, boundary) from an OpenAPI / Swagger specification.

Automation

Go deeper

Your progress on this topic

Part of the QA Roadmap