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
/v1after/v2shipped; 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.
| OWASP API entry | What goes wrong | One safe check | Expected |
|---|---|---|---|
| API1:2023Broken Object Level Authorization (BOLA) | An id from the client is trusted without checking that the caller may access that object | The same request with another user's token, same role | 404 or 403; no data of the other user |
| API2:2023Broken Authentication | Tokens accepted after expiry or logout; unlimited password or OTP attempts | No token, expired, revoked and tampered token; count the wrong OTP attempts | 401 with WWW-Authenticate and no data in the body; 429 or a lockout after a few attempts |
| API3:2023Broken Object Property Level Authorization | The 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 back | Only the fields in the contract; 400, or the field ignored and unchanged |
| API4:2023Unrestricted Resource Consumption | No limit on request rate, page size, upload size or operations that cost money | Repeat quickly with agreed numbers; ask for pageSize=50000; upload one byte over the maximum | 429, 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 function | A customer token on an admin endpoint, from the users × requests matrix of the API authorization testing lesson | 403 or 404; nothing changed |
| API6:2023Unrestricted Access to Sensitive Business Flows | A flow that harms the business when automated has no limit, step-up or review | Script the flow with one account | Something 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-list | Supply a capture URL you control; ask the developer how private addresses are refused | Only allow-listed hosts are fetched |
| API8:2023Security Misconfiguration | Stack traces in errors, HTTP without TLS, CORS for any origin, unneeded methods, default settings | One malformed request; read the response headers | A generic error without internals; headers as agreed with the team |
| API9:2023Improper Inventory Management | Old versions and debug endpoints still answer; the OpenAPI document and the deployed API differ | Call /v1 after /v2 shipped; list what answers but is not documented | Retired versions refuse; documentation and deployment match |
| API10:2023Unsafe Consumption of APIs | Data from a partner API is trusted without validation, size limits or redirect checks | Mock the partner and return bad data | Validated 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
429or 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
429appears, 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. Expect400or413and 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/invoicesreturned200with A's invoices instead of404. 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, no429, no lockout. Critical: the login code can be guessed. - C5, excessive data exposure:
GET /v1/users/mereturnednationalIdandpasswordHash, neither of which is in the contract. Critical: identity data and a password hash reach any client. - C6, mass assignment:
PATCH /v1/users/mewith"planTier": "premium"returned200, and a freshGETshowed the plan upgraded. Major, with a financial impact. - C7, resource consumption:
?pageSize=50000on 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 31Common 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
403whose body still contains the object is a leak, and a200does 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
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.
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- For each endpoint E1 to E5, name the OWASP API Security Top 10 entries to check; several may apply.
- Write one safe check per entry: actor, request, expected result.
- Judge O1 to O3: correct or defect, with the OWASP entry and a severity.
- Name one check you must not run in this environment without agreement, and why.
Show the 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
- OWASP API Security Top 10 (2023)The ten most common API weaknesses: broken object-level authorization, broken authentication, unrestricted resource consumption and more.
- OWASP Cheat Sheet Series: REST SecuritySecurity rules for REST APIs: HTTPS, authentication on every request, input validation, safe error responses, rate limiting.
- RFC 6585: Additional HTTP Status CodesDefines 428, 429 Too Many Requests, 431 and 511, including the Retry-After header for rate limiting.
- RFC 8725: JSON Web Token Best Current PracticesThe security rules for JWT implementations: reject alg=none, pin algorithms, validate iss/aud, use short lifetimes.
Practise with Mutqan tools
Test Bearer, Basic, API-key and custom-header authentication against an endpoint and compare authorised vs unauthorised responses.
Inspect the JOSE header of a JWT: alg, typ, kid, jku, x5u and potential algorithm-confusion risks.
Analyze JWT claims for missing, risky or non-standard values and get security recommendations.
Analyze an API response: structure, field types, nulls, duplicates, sizes and potential data-quality issues.
Test REST endpoints with full control over method, headers and JSON body, then assert on status, headers and JSON paths.
Generate a structured test-case matrix (positive, negative, auth, boundary) from an OpenAPI / Swagger specification.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.