Skip to content
guide

OWASP API Security Top 10: checklist with fixes

| 12 min read
Cybersecurity command center with multiple monitoring screens
Photo by Adi Goldstein on Unsplash

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.

Cybersecurity command center with multiple monitoring screens
Each OWASP API risk maps to a specific defensive control you can test and verify Photo by Adi Goldstein on Unsplash

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.