The OWASP API Security Top 10

SecurityAPIBackendOWASP
Share on LinkedIn

The OWASP API Security Top 10 is the checklist your API should pass before it handles real user data — and BOLA (Broken Object Level Authorization) is number one because it's embarrassingly easy to exploit and devastatingly common. I've reviewed APIs where changing user_id=123 to user_id=124 in the request returned another user's entire profile. No hacking tools needed — just increment a number. The Top 10 isn't theoretical; each entry maps to vulnerabilities I've found in production code reviews.

The list (2023 edition)

# Risk One-line summary
API1 Broken Object Level Authorization (BOLA) Access other users' resources by changing IDs
API2 Broken Authentication Weak tokens, credential stuffing, no MFA
API3 Broken Object Property Level Authorization Mass assignment, excessive field exposure
API4 Unrestricted Resource Consumption No rate limits, expensive queries
API5 Broken Function Level Authorization Regular users access admin endpoints
API6 Unrestricted Access to Sensitive Business Flows No bot protection on critical flows
API7 Server Side Request Forgery (SSRF) API fetches attacker-controlled URLs
API8 Security Misconfiguration Default creds, verbose errors, missing headers
API9 Improper Inventory Management Shadow APIs, deprecated endpoints exposed
API10 Unsafe Consumption of APIs Trusting third-party API responses

API1: BOLA — the big one

Every endpoint with a resource ID is vulnerable unless you check ownership:

# VULNERABLE
@app.get("/api/orders/{order_id}")
def get_order(order_id: str, user: User = Depends(get_current_user)):
    return db.get_order(order_id)  # any user can access any order

# FIXED
@app.get("/api/orders/{order_id}")
def get_order(order_id: str, user: User = Depends(get_current_user)):
    order = db.get_order(order_id)
    if order.user_id != user.id:
        raise HTTPException(403, "Not authorized")
    return order

Better: enforce at the data layer:

def get_order_for_user(order_id: str, user_id: str) -> Order:
    order = db.query("SELECT * FROM orders WHERE id = %s AND user_id = %s", order_id, user_id)
    if not order:
        raise NotFound()  # don't reveal whether order exists
    return order

Test: create two users, authenticate as user A, try to access user B's resources. Automate this in integration tests.

API2: Broken Authentication

Mitigations:

API3: Excessive Data Exposure

Return only what the client needs:

# VULNERABLE — returns all fields including internal ones
return user  # {id, email, password_hash, ssn, internal_notes, ...}

# FIXED — explicit response model
class UserResponse(BaseModel):
    id: str
    name: str
    email: str

return UserResponse(id=user.id, name=user.name, email=user.email)

Use response DTOs/serializers that whitelist fields. Never return database models directly.

API4: Unrestricted Resource Consumption

Implement rate limiting at the gateway and service level:

API5: Broken Function Level Authorization

Separate admin and user routes with explicit role checks:

def require_admin(user: User = Depends(get_current_user)):
    if "admin" not in user.roles:
        raise HTTPException(403)
    return user

@app.delete("/api/admin/users/{user_id}", dependencies=[Depends(require_admin)])
def delete_user(user_id: str):
    ...

Don't rely on "security through obscurity" — admin endpoints on non-obvious paths are still discoverable.

API7: SSRF

When your API fetches URLs on behalf of users:

# VULNERABLE
@app.post("/api/fetch")
def fetch_url(url: str):
    return requests.get(url).text  # attacker fetches internal services

# FIXED
ALLOWED_DOMAINS = {"api.example.com", "cdn.example.com"}

def safe_fetch(url: str):
    parsed = urlparse(url)
    if parsed.hostname not in ALLOWED_DOMAINS:
        raise BadRequest("Domain not allowed")
    if parsed.hostname in ("localhost", "169.254.169.254"):
        raise BadRequest("Internal URLs blocked")
    return requests.get(url, timeout=5, allow_redirects=False)

Block internal IPs, metadata endpoints, and redirect chains.

Security review checklist

Before shipping any API endpoint:

Run this checklist in PR review for every new endpoint. Automate what you can — static analysis tools catch some patterns, but BOLA requires intentional authorization logic.

Common production mistakes

Teams get security owasp api top 10 wrong in predictable ways:

API design for security owasp api top 10 frustrates clients when pagination cursors expire silently, error bodies lack stable machine-readable codes, and rate limits return 429 without Retry-After headers.

Debugging and triage workflow

When security owasp api top 10 misbehaves in production, work top-down instead of guessing:

  1. Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
  2. Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
  3. Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
  4. Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
  5. Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
  6. Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.

Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.

Resources

Frequently asked questions

What is the OWASP API Security Top 10?

The OWASP API Security Top 10 is a standard awareness document listing the most critical security risks for APIs. It covers broken object-level authorization, broken authentication, excessive data exposure, lack of rate limiting, and other common API vulnerabilities. It serves as a checklist for API security reviews and threat modeling.

What is the most common API security vulnerability?

Broken Object Level Authorization (BOLA, API1) — where an API endpoint accepts a resource ID without verifying the requesting user owns or has permission to access that resource. Example: changing /api/orders/123 to /api/orders/456 and accessing another user's order. It accounts for the majority of API breaches.

How do I protect against BOLA?

Never trust client-supplied resource IDs without authorization checks. Every endpoint that accesses a resource by ID must verify the authenticated user has permission to access that specific resource. Use policy engines or middleware that checks ownership before returning data.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →