OWASP API Security Top 10: checklist with fixes
Your API serves 10,000 requests per hour. Three of those requests come from an attacker who changes
an order_id parameter and downloads every customer record in your database. You find
out when your users do, on Twitter.
The OWASP API Security Top 10 (2023 edition) catalogs the ten risks responsible for the majority of
API breaches. This guide walks through each risk with a one-paragraph explanation, a realistic attack
scenario, and a concrete fix. Where a botoi API endpoint helps you detect or prevent the risk, the
relevant endpoint is included with a working curl command.
API1:2023 Broken object-level authorization (BOLA)
BOLA occurs when an API returns data based on an object ID without checking whether the requesting
user owns that object. An attacker calls GET /api/orders/1001, gets data, then iterates
through 1002, 1003, and so on. Every record in the table is now exposed.
BOLA has ranked as the number one API risk since the first OWASP API Security list in 2019.
Attack scenario: A food delivery app exposes GET /api/orders/:id.
An attacker writes a loop from 1 to 100,000 and downloads every order, including delivery addresses,
phone numbers, and payment method details.
Here is the vulnerable code:
// Vulnerable: no ownership check
app.get("/api/orders/:id", async (req, res) => {
const order = await db.orders.findById(req.params.id);
res.json(order); // Any user can read any order
}); And here is the fix:
// Fixed: verify the requesting user owns the resource
app.get("/api/orders/:id", async (req, res) => {
const order = await db.orders.findById(req.params.id);
if (!order) return res.status(404).json({ error: "Not found" });
if (order.userId !== req.user.id) {
return res.status(403).json({ error: "Forbidden" });
}
res.json(order);
});
Every endpoint that accepts an ID from the client needs an ownership check. No exceptions. Use
middleware or a query filter (e.g., WHERE user_id = ?) to enforce this at the data layer.
API2:2023 Broken authentication
Broken authentication covers weak token generation, missing token validation, credential stuffing attacks, and endpoints that accept expired or tampered tokens. Attackers target login endpoints with lists of breached credentials, or they steal tokens from insecure storage.
Attack scenario: An attacker obtains a list of 10 million email/password pairs from
a data breach. They write a script that tests each pair against your POST /api/login
endpoint. Your API has no rate limit on login attempts, so the attacker compromises 2,000 accounts in
an hour.
Fix: Enforce rate limiting on authentication endpoints (5 attempts per minute per IP). Require multi-factor authentication for sensitive operations. Check user credentials against known breaches before allowing account creation.
The botoi breach check API tells you whether an email address appeared in known data breaches:
curl -s -X POST https://api.botoi.com/v1/breach/check \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com"
}' Response:
{
"success": true,
"data": {
"breached": true,
"count": 3,
"breaches": [
{ "name": "ExampleCorp", "date": "2024-01-15", "dataTypes": ["email", "password"] },
{ "name": "DataDump2023", "date": "2023-08-22", "dataTypes": ["email", "username"] },
{ "name": "LeakedDB", "date": "2022-11-03", "dataTypes": ["email", "phone"] }
]
}
}
Call /v1/breach/check during signup or password reset. If the email appears in breaches,
prompt the user to pick a stronger, unique password and enable two-factor authentication.
API3:2023 Broken object property-level authorization
This risk combines mass assignment and excessive data exposure. Mass assignment happens when an API
binds request body fields directly to a data model without filtering. A user sends
"role": "admin" in a profile update and gets elevated privileges. Excessive data exposure
happens when an API returns internal fields (database IDs, hashed passwords, admin flags) the client
should never see.
Attack scenario: A SaaS app lets users update their profile via
PUT /api/users/:id. The backend accepts the full request body and writes it to the database.
An attacker adds "plan": "enterprise" to the request and gets free access to premium features.
Fix: Use explicit allowlists for writable fields. Never bind raw request data to your data model. Use separate response DTOs that exclude internal fields. Validate every incoming property against a schema before processing.
API4:2023 Unrestricted resource consumption
APIs that accept unbounded requests let attackers run up your infrastructure bill, exhaust database
connections, or trigger denial-of-service. This applies to missing rate limits, unbounded query
parameters (e.g., ?limit=1000000), file uploads with no size cap, and endpoints that
trigger expensive background jobs.
Attack scenario: An API endpoint generates PDF reports. An attacker sends 500 concurrent requests, each requesting a 200-page report. Your worker pool fills up, and legitimate users get 503 errors for the next 20 minutes.
Fix: Add rate limiting per user and per IP. Cap pagination limits with server-side maximums. Set file upload size limits. Use queue-based processing for expensive operations.
// Express rate limiter per user
import rateLimit from "express-rate-limit";
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 30, // 30 requests per minute per IP
standardHeaders: true,
legacyHeaders: false,
message: { error: "Rate limit exceeded. Try again in 60 seconds." },
});
app.use("/api/", apiLimiter); API5:2023 Broken function-level authorization
Function-level authorization flaws let regular users call admin endpoints. The typical pattern: an
admin panel calls DELETE /api/admin/users/:id. The frontend hides the button from
non-admin users, but the API endpoint itself does not check roles. Any authenticated user who
discovers the URL can delete accounts.
Attack scenario: A developer finds /api/admin/export-all-users
in the JavaScript bundle. They call it with their regular user token and download the full user
database.
Fix: Check the user's role at the API layer for every admin endpoint. Do not rely on
the frontend to hide functionality. Group admin routes behind middleware that verifies
role === "admin" before processing the request.
API6:2023 Unrestricted access to sensitive business flows
Some API flows are dangerous at scale even when each individual request is authorized. Buying limited-inventory items, creating free trial accounts, submitting referral codes; these flows break when automated. A bot signs up 10,000 free accounts or buys every ticket in a flash sale before a real user loads the page.
Attack scenario: A sneaker reseller writes a bot that calls
POST /api/checkout 500 times in the first second of a limited drop. Every pair sells
to bots. Human customers see "sold out" before the page finishes loading.
Fix: Add CAPTCHA or proof-of-work challenges to high-value flows. Track device fingerprints to detect automation. Set per-user purchase limits. Use queue-based access for flash sales instead of first-come-first-served endpoints.
API7:2023 Server-side request forgery (SSRF)
SSRF happens when an API accepts a URL from the client and fetches it server-side without
validating the target. An attacker provides http://169.254.169.254/latest/meta-data/
and your server returns AWS instance credentials. Or they target
http://localhost:6379/ and interact with your Redis instance.
Attack scenario: A webhook integration lets users specify a callback URL.
An attacker sets the callback to http://169.254.169.254/latest/meta-data/iam/security-credentials/
and receives your cloud provider's IAM role credentials in the webhook payload.
// Block internal network requests
const BLOCKED_RANGES = [
/^10\./, /^172\.(1[6-9]|2\d|3[01])\./, /^192\.168\./,
/^127\./, /^0\./, /^169\.254\./,
/^localhost$/i, /^\[::1\]$/,
];
function isSafeUrl(urlString) {
try {
const url = new URL(urlString);
const hostname = url.hostname;
return !BLOCKED_RANGES.some((re) => re.test(hostname));
} catch {
return false;
}
}
app.post("/api/fetch-url", async (req, res) => {
const { url } = req.body;
if (!isSafeUrl(url)) {
return res.status(400).json({ error: "URL targets a blocked network range" });
}
// proceed with external fetch
}); Fix: Validate and allowlist destination URLs. Block private IP ranges and cloud metadata endpoints. Resolve DNS before making the request to prevent DNS rebinding. Run outbound requests from an isolated network segment.
API8:2023 Security misconfiguration
Misconfiguration is the broadest risk category. It includes missing CORS restrictions, verbose error messages that leak stack traces, default credentials on admin panels, unnecessary HTTP methods enabled, TLS not enforced, and missing security headers. Any single misconfiguration creates an entry point.
Attack scenario: An API returns full stack traces in production error responses. An attacker triggers errors on purpose, reads the stack traces, identifies the ORM and database version, then crafts an injection payload based on known vulnerabilities in that version.
Fix: Strip stack traces from production responses. Set CORS to allow only your domains. Disable HTTP methods you do not use. Enforce TLS everywhere. Run automated security header checks. Sanitize all HTML output to prevent XSS.
The botoi HTML sanitize API strips malicious tags and attributes from user-supplied HTML:
curl -s -X POST https://api.botoi.com/v1/html-sanitize \
-H "Content-Type: application/json" \
-d '{
"html": "<p>Hello</p><script>document.cookie</script><img onerror=alert(1) src=x>"
}' Response:
{
"success": true,
"data": {
"sanitized": "<p>Hello</p><img src=\"x\">"
}
}
The <script> tag and the onerror attribute are removed. The safe HTML
is returned. Call this endpoint before storing or rendering any user-provided HTML.
API9:2023 Improper inventory management
Old API versions, forgotten staging endpoints, and undocumented routes create attack surface you
do not monitor. Attackers scan for /v1/, /v2/, /api-dev/,
and /internal/ paths. They find a deprecated endpoint that lacks the security controls
you added to the current version.
Attack scenario: Your team shipped /v2/users with role-based access
control. But /v1/users still runs in production without any authorization. An attacker
discovers the v1 path via a public Swagger file and pulls the entire user table.
Fix: Maintain a complete API inventory. Decommission old versions; do not leave them running "in case someone still uses them." Gate every version behind the same auth middleware. Scan your own infrastructure for exposed paths on a regular schedule.
API10:2023 Unsafe consumption of APIs
Your API calls third-party services: payment processors, email providers, geocoding APIs. If you trust their responses without validation, a compromised or malicious third-party can inject data into your system. This includes trusting redirect URLs, parsing unvalidated JSON, or storing third-party responses without sanitization.
Attack scenario: Your app fetches company data from a third-party enrichment API
and stores the company_name field directly in your database. The enrichment API gets
compromised, and the attacker injects <script> tags into company names. Every
user who views a company profile in your dashboard executes the script.
Fix: Validate and sanitize all third-party responses before storing or rendering them. Use schema validation on incoming data. Set timeouts on external calls. Apply the same input validation to third-party data that you apply to user input.
Detect sensitive data exposure in your API responses
Risks API1 and API3 often result in sensitive data leaving your API. The botoi PII detection API scans text for Social Security numbers, credit card numbers, email addresses, phone numbers, IP addresses, and dates of birth. Run it against your API response bodies in integration tests to catch accidental data leaks before deployment.
curl -s -X POST https://api.botoi.com/v1/pii/detect \
-H "Content-Type: application/json" \
-d '{
"text": "Customer SSN is 123-45-6789 and card is 4111111111111111"
}' Response:
{
"success": true,
"data": {
"found": true,
"count": 2,
"findings": [
{
"type": "ssn",
"value": "123-45-6789",
"start": 17,
"end": 28,
"masked": "***-**-6789"
},
{
"type": "credit_card",
"value": "4111111111111111",
"start": 42,
"end": 58,
"masked": "************1111"
}
]
}
} If your API response triggers findings, your endpoint is returning data the client should not see. Fix the query or DTO to exclude those fields.
Encrypt sensitive data at rest
When your API stores sensitive configuration, tokens, or credentials, encrypt them before writing to the database. The botoi encryption API provides AES-256-CBC encryption:
curl -s -X POST https://api.botoi.com/v1/encrypt/encrypt \
-H "Content-Type: application/json" \
-d '{
"text": "sensitive-api-token-abc123",
"password": "your-secret-key"
}' Response:
{
"success": true,
"data": {
"encrypted": "U2FsdGVkX1+abc...encrypted_output_here",
"algorithm": "aes-256-cbc"
}
}
Store the encrypted output. Decrypt with /v1/encrypt/decrypt only when the value is
needed. This limits the blast radius if an attacker gains read access to your database through a
BOLA or SQL injection vulnerability.
The OWASP API Security Top 10 checklist
Print this table or add it to your security review template. Check each item before any API release.
| Risk | Check | botoi endpoint |
|---|---|---|
| API1 BOLA | Every endpoint that accepts an object ID verifies ownership | |
| API2 Broken Auth | Login rate-limited; tokens expire; MFA on sensitive ops | /v1/breach/check |
| API3 Property Auth | Request fields allowlisted; response DTOs exclude internals | /v1/pii/detect |
| API4 Resource Limit | Rate limits, pagination caps, file size limits enforced | |
| API5 Function Auth | Admin endpoints check role at API layer, not frontend | |
| API6 Business Flow | CAPTCHA/proof-of-work on high-value flows; per-user limits | |
| API7 SSRF | User-supplied URLs validated; private ranges blocked | |
| API8 Misconfig | No stack traces; CORS restricted; HTML sanitized | /v1/html-sanitize |
| API9 Inventory | Old API versions decommissioned; all routes documented | |
| API10 Unsafe Consumption | Third-party responses validated and sanitized before storage | /v1/html-sanitize |
Where to go from here
The full OWASP API Security Top 10 documentation lives at owasp.org/API-Security. Each risk page includes detection methods, example attack scenarios, and references to CWEs.
For automated checks, integrate the botoi endpoints above into your CI pipeline. Run PII detection against API response fixtures. Sanitize stored HTML on write. Check new user emails against breach databases during signup. These are small additions to your test suite that catch the risks scanners miss.
Frequently asked questions
- What is the OWASP API Security Top 10?
- The OWASP API Security Top 10 is a list of the ten most critical API security risks, maintained by the Open Worldwide Application Security Project. The 2023 edition covers broken object-level authorization, broken authentication, broken object property-level authorization, unrestricted resource consumption, broken function-level authorization, unrestricted access to sensitive business flows, server-side request forgery, security misconfiguration, improper inventory management, and unsafe consumption of APIs.
- How often is the OWASP API Security Top 10 updated?
- OWASP released the first API Security Top 10 in 2019 and updated it in 2023. There is no fixed update schedule. The project team collects data from security incidents, bug bounty reports, and community contributions, then publishes a new edition when the threat landscape changes enough to warrant one.
- What is BOLA and why is it the number one API risk?
- BOLA (Broken Object-Level Authorization) happens when an API endpoint accepts an object ID from the client and returns data without verifying the requesting user owns that object. An attacker changes the ID in the request and accesses another user's data. It ranks first because it is common, easy to exploit, and often exposes sensitive records at scale.
- Can automated tools catch all OWASP API Security Top 10 risks?
- No. Automated scanners catch misconfiguration (API8), missing rate limits (API4), and some injection patterns (via API8). But authorization flaws (API1, API3, API5) require business logic understanding that scanners lack. You need manual code review, penetration testing, and architecture-level checks for complete coverage.
- How do I prioritize which OWASP API risks to fix first?
- Start with API1 (BOLA) and API2 (Broken Authentication) because they lead to direct data breaches. Then address API4 (Unrestricted Resource Consumption) to prevent denial-of-service. After that, work through the remaining risks based on which endpoints handle the most sensitive data in your application.
Try this API
Password Breach Check API — interactive playground and code examples
More guide posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.