API Design

Security · Lesson 01

API Security Threats — the full landscape

Before you can defend an API you need a map of what can go wrong. This lesson builds that map: the three properties every attacker tries to destroy, the ten most-exploited API weakness classes, and the layered model that keeps attackers from a clean shot.

⏱ 14 min Difficulty: core Prereq: HTTP basics

By the end you'll be able to

Start with what you are protecting

Security is not a feature you bolt on at the end — it is a property you either designed in or accidentally designed out. Before picking any control, ask: what are you protecting, and from whom?

The classical answer is the CIA triad — three properties that, if any one is violated, you have a security incident:

PropertyWhat it means for an APIThreat that breaks it
ConfidentialityData is visible only to authorised partiesEavesdropping, data leaks, over-broad responses
IntegrityData is not tampered with in transit or at restMan-in-the-middle modification, injection attacks
AvailabilityThe API responds to legitimate requestsDDoS, resource exhaustion, lack of rate limiting

Think of your API as a delivery van carrying parcels. Confidentiality means the parcels stay sealed (no peeking). Integrity means the contents cannot be swapped during transit (the seals are tamper-evident). Availability means the van arrives on time (it is not blocked at a checkpoint or run off the road). An attacker only needs to break one of the three.

Threat modelling in four questions

A threat model is a structured way to answer: "what could go wrong?" before you build, not after you're breached. You don't need a formal notation; four questions get you 80% of the way:

  1. What are you building? — list the API endpoints and the data they touch.
  2. What can go wrong? — walk through each endpoint: what if the caller is not who they say, or sends a crafted input?
  3. What will you do about it? — pick a countermeasure for each threat.
  4. Did it work? — test your controls; repeat.

The OWASP API Security Top 10

OWASP (the Open Web Application Security Project) publishes a ranked list of the most-exploited API weaknesses, updated periodically. The 2023 edition is the current reference. Each category represents a real pattern seen across real breaches — these are not academic edge-cases.

#NameOne-line summary
API1Broken Object-Level Authorization (BOLA)Accessing another user's resource by changing an ID in the URL — the most common API flaw.
API2Broken AuthenticationWeak tokens, missing expiry, passwords accepted in query strings.
API3Broken Object Property-Level AuthorizationEndpoint returns (or accepts) fields the caller should never see or set.
API4Unrestricted Resource ConsumptionNo rate limits, no pagination caps — one caller can exhaust CPU, DB, or bandwidth.
API5Broken Function-Level AuthorizationAdmin-only endpoints are accessible to normal users because the check is missing, not hidden.
API6Unrestricted Access to Sensitive Business FlowsBots can abuse legitimate features (mass account creation, bulk coupon redemption).
API7Server-Side Request Forgery (SSRF)API fetches a URL supplied by the client, letting attackers probe internal services.
API8Security MisconfigurationOpen S3 buckets, debug mode in prod, missing CORS policy, default credentials left in place.
API9Improper Inventory ManagementOld API versions, shadow endpoints, undocumented endpoints — attackers find what you forgot you had.
API10Unsafe Consumption of APIsTrusting third-party API responses without validation — you inherit their vulnerabilities.

Notice that seven of the ten are authorisation and access-control failures — not cryptography, not exotic exploits. The most exploited API bugs are "can this caller do this thing?" checks that are missing or wrong.

BOLA up close — the #1 bug

Broken Object-Level Authorization is so common it earned the top spot. The pattern is simple: a caller fetches a resource by ID, and the server checks only that the caller is authenticated — not that the caller owns the resource.

# Attacker is user 99. They try user 42's invoice:
GET /v1/invoices/42
Authorization: Bearer <valid_token_for_user_99>

# Vulnerable server response — no ownership check:
HTTP/1.1 200 OK
{
  "id": 42,
  "owner_id": 501,
  "amount": "$9,200",
  "card_last4": "4242"
}

# Fix — check ownership server-side every time:
# SELECT * FROM invoices WHERE id=42 AND owner_id=99
# → 0 rows → 404 Not Found (or 403 Forbidden)

Defense in depth — the layered model

No single control is unbreakable. Defense in depth means stacking independent layers so that an attacker who defeats one layer still faces the next. The classic analogy is a medieval castle: a moat, then a wall, then a gatehouse, then inner guards — each layer buys time and raises cost for the attacker.

For an API the layers look like this (outer to inner):

Layer 4 — Network perimeter / WAF / DDoS protection Layer 3 — API Gateway · TLS termination · Rate limiting · Auth token validation Layer 2 — Application service · Input validation · Business-logic authz (BOLA checks) Data store Parameterised queries · Encryption at rest · Least-privilege DB user Audit logging · Secrets in vault, not in code Attacker
Each ring is an independent defense. An attacker must defeat all four rings to reach the data. Defeating the WAF does not grant database access.
🎯 Interview angle

When asked "how would you secure this API?" start with a threat model, not a list of tools. Say: "First I'd identify what data is at stake and who the callers are. Then I'd walk through authorization checks at every endpoint, validate all inputs, enforce TLS, and add rate limits." That structure shows you think in layers, not in a product catalogue.

⚠️ Common trap

Treating authentication as sufficient for authorization. A caller with a valid token is authenticated — you know who they are. That says nothing about what they're allowed to do. Most OWASP Top 10 entries are authorization failures, not authentication failures. Always ask both: "Is this caller who they say?" and "Are they allowed to do this to this resource?"

✅ Do this, not that

Do build a short threat model (even a notes file) before writing the first endpoint. Don't wait until a security review or a production incident. BOLA bugs are trivially easy to prevent at design time — they are expensive to fix after the schema and the clients have frozen.

Under the hood: how it actually works

A BOLA attack runs in three phases: enumerate (guess or discover valid resource IDs), access (call the endpoint with a different user's ID using your own valid token), and exfiltrate (read the response and repeat). No special tooling is required — a loop in a shell script is enough.

# ── Phase 1: Attacker authenticates as their own account (user_99) ──────────
POST /v1/auth/token
{ "username": "attacker@example.com", "password": "hunter2" }

HTTP/1.1 200 OK
{ "token": "eyJ...", "user_id": 99 }

# ── Phase 2: Attacker views their own order to learn the URL pattern ─────────
GET /v1/orders/123
Authorization: Bearer eyJ...

HTTP/1.1 200 OK
{
  "id": 123,
  "owner_id": 99,
  "total": "$42.00"
}

# ── Phase 3: Attacker increments the ID — vulnerable server has no ownership check
GET /v1/orders/124
Authorization: Bearer eyJ...

# Vulnerable server — returns another user's order with PII:
HTTP/1.1 200 OK
{
  "id": 124,
  "owner_id": 501,
  "email": "victim@example.com",
  "address": "12 Maple St, Springfield",
  "card_last4": "7734"
}

# ── Phase 4: Attacker iterates 124 → 9999 in a loop to dump all orders ──────
# for id in $(seq 124 9999); do
#   curl -s -H "Authorization: Bearer eyJ..." /v1/orders/$id >> dump.json
# done

# ── Phase 5: Fixed server — ownership check blocks the request ───────────────
# Server SQL: SELECT * FROM orders WHERE id=124 AND owner_id=99 → 0 rows
HTTP/1.1 403 Forbidden
{ "error": "forbidden" }
Attack phase How it runs (on the wire) How to detect it
ID enumeration probe Attacker sends sequential requests to a resource endpoint (e.g. /orders/100, /orders/101 …) using their own valid token, watching for 200 vs 404/403 to map which IDs exist. Spike in requests to the same endpoint from one token within a short window; sequential numeric IDs in URL; high 404 rate from a single IP or token.
BOLA access A single authenticated request to a resource the token owner does not own; the server returns 200 with another user's data instead of 403/404. Response body contains a user_id / owner_id that does not match the token's subject claim; cross-user access logged at the application layer.
Bulk exfiltration Automated loop (shell, Python, Burp Intruder) iterates a large ID range; requests arrive faster than human browsing, often without think-time between calls. Request rate for a resource endpoint from one token exceeds a normal-use threshold (e.g. >30 req/min); anomaly detection on per-token resource access count; SIEM alert on sustained sequential ID pattern.

How to debug & inspect it

Finding BOLA in your own API does not require a scanner. Access logs already contain the evidence — you just need to know what to look for.

# Scan access logs for sequential ID probing on /orders by a single token $ grep 'GET /v1/orders/' access.log | grep ' 200 ' | awk '{print $7}' | sort | uniq -c | sort -rn | head -20 312 /v1/orders/4821 311 /v1/orders/4822 309 /v1/orders/4823 # Seeing the same IP or token racking up sequential IDs is a red flag # Count how many distinct resource IDs one token accessed in the last hour $ awk '/GET \/v1\/orders\// {match($0, /Bearer ([^ ]+)/, tok); match($0, /\/orders\/([0-9]+)/, rid); print tok[1], rid[1]}' access.log \ | sort | uniq | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 4876 eyJ0eXAi... ← one token accessed 4876 unique order IDs 3 eyJ0eXAj... ← normal user: 3 orders # Any token with a per-hour resource-ID count far above your median is suspicious
Symptom Cause Fix
Spike in 403s on /resource/{id} from one IP BOLA probe — attacker is hitting IDs they do not own; the server is correctly returning 403 but the probe is still happening. Block / rate-limit the IP; audit which IDs it accessed; verify none returned 200.
Unusually high per-user request count for a resource endpoint Bulk enumeration — attacker iterating IDs in a loop, possibly at high speed. Rate limit per-token per-endpoint; alert on more than N requests to resource/{id} within 1 minute.
A user ID appears in responses that does not match the authenticated user Missing per-object ownership check — the server verified the token but not that the caller owns the fetched record. Add WHERE owner_id = current_user to every query that fetches a resource by ID; never rely on client-supplied owner hints.
Sequential numeric IDs in the URL Predictable IDs make BOLA trivial to exploit — an attacker can enumerate the entire ID space mechanically. Use UUIDs or opaque IDs; sequential IDs alone are not a vulnerability but they remove enumeration friction entirely.
⚠️ Audit checklist — find BOLA in your own API
  1. List every GET/PUT/PATCH/DELETE endpoint that accepts a resource ID (e.g. /orders/{id}, /users/{id}/profile). These are all candidates for BOLA.
  2. For each endpoint, find the database query it executes and check whether it filters on owner_id = current_user (or equivalent). A query like SELECT * FROM orders WHERE id = ? with no ownership predicate is vulnerable.
  3. Write a test with two user accounts: authenticate as user A, then call a resource that belongs to user B. The response must be 403 or 404 — if it is 200, you have BOLA.
  4. Check indirect object references too — not just top-level IDs. If your API exposes nested resources (/orders/{order_id}/items/{item_id}), verify that the item actually belongs to that order and that the order belongs to the caller.
  5. Review your logs for the patterns above: sequential-ID runs from a single token, cross-user owner_id in 200 responses, and anomalous per-token resource access counts.

🧠 Quick check

1. An attacker intercepts API traffic and changes the JSON payload in a response before it reaches the client. Which CIA property did they violate?

Integrity means data cannot be altered in transit without detection. Swapping payload contents is a classic integrity attack. TLS prevents this — that is lesson 02.

2. A caller sends GET /orders/9999 using their own valid token, but order 9999 belongs to a different user and they receive it anyway. Which OWASP category is this?

The caller is authenticated (valid token) but the server failed to check that the caller owns order 9999. That is textbook BOLA — missing per-object ownership check.

3. Which statement best describes "defense in depth"?

Defense in depth means multiple independent barriers. No single layer is assumed perfect; each raises the attacker's cost and limits blast radius when one layer fails.

4. Which OWASP category covers an API endpoint that lets a bot create 10,000 fake accounts in minutes because there is no rate limit?

API6 covers abuse of legitimate business logic at scale. The endpoint works as designed — but without bot detection or rate limiting, automated abuse is trivial.

✍️ Exercise: threat-model a ride-share API (try before opening)

You are designing an API for a ride-share app. The key endpoints are: POST /rides (request a ride), GET /rides/{id} (track your ride), GET /drivers/{id}/location (see driver position). For each endpoint, name one threat and one countermeasure.

Model answer:

EndpointThreatCountermeasure
GET /rides/{id}BOLA — rider A reads rider B's tripServer-side: SELECT … WHERE id=X AND rider_id=current_user
POST /ridesBot spam — 1000 fake ride requests exhaust driver supplyRate limit per account + CAPTCHA on suspicious volume
GET /drivers/{id}/locationStalking — anyone can track any driverOnly expose location to the matched rider for the active trip; return coarse position otherwise

Rubric: ✓ identified the correct OWASP category for each threat ✓ countermeasure is applied server-side, not client-side ✓ answers are proportionate (coarse location for privacy, not just "add auth").

Key takeaways

Sources & further reading