Who this is for
- API Engineers and backend developers shipping REST/GraphQL services.
- Engineers adding authentication/authorization, rate limits, or input validation.
- Tech leads and security champions who review API designs and PRs.
Prerequisites
- Basic HTTP knowledge: methods, headers, status codes, JSON.
- Familiarity with API auth basics (tokens, sessions, OAuth/JWT).
- Comfort reading simple pseudocode and API specs.
Why this matters
APIs expose core business data and actions. A single missed check can leak user data, drain resources, or allow attackers to perform admin tasks.
- Real task: ensure only the owner can access
/orders/{id}(BOLA defense). - Real task: limit abusive scraping of
/search(resource consumption). - Real task: protect admin-only endpoints (function-level auth).
- Real task: sanitize and restrict server-side URL fetchers (SSRF).
- Real task: maintain versioned inventories of internal/external APIs.
Note: The quick test is available to everyone; sign in to save your progress.
Concept explained simply
The OWASP API Security Top 10 is a curated list of the most critical API risks. It helps you prioritize what to defend first. Learn the names, recognize the patterns, and apply standard mitigations.
Mental model
Imagine your API as a building:
- Identity at the door (Authentication): only real guests get a badge.
- Correct room access (Authorization): even with a badge, you only enter allowed rooms.
- Utilities with limits (Resource consumption): lights and water have meters to prevent overuse.
- No open windows to dangerous places (SSRF): you restrict where windows can open.
- Clear building map (Inventory): you track every entrance and service door.
OWASP API Security Top 10 (2023) at a glance
- API1: Broken Object Level Authorization (BOLA) — Accessing another user’s object by changing an ID.
- API2: Broken Authentication — Weak login, token handling, or session management.
- API3: Broken Object Property Level Authorization (BOPLA) — Accessing or modifying properties/fields you shouldn’t.
- API4: Unrestricted Resource Consumption — No rate limits, pagination, or heavy job controls.
- API5: Broken Function Level Authorization (BFLA) — Calling admin or premium functions without proper role checks.
- API6: Unrestricted Access to Sensitive Business Flows — Automating high-value flows (e.g., checkout abuse) with no friction.
- API7: Server-Side Request Forgery (SSRF) — Backend fetches attacker-controlled URLs.
- API8: Security Misconfiguration — Defaults left open, verbose errors, missing headers.
- API9: Improper Inventory Management — Unknown endpoints, shadow APIs, old versions alive.
- API10: Unsafe Consumption of APIs — Blindly trusting data from third-party/internal APIs.
Tip: Prioritize quickly
Start with API1, API2, API5. These three cause many impactful incidents and are quick to check in code reviews and tests.
Worked examples
1) BOLA in order details
Vulnerable: GET /api/orders/12345 returns the order if it exists. The handler loads by ID only.
{
// Pseudocode
order = db.orders.findById(path.id)
return order // No ownership check!
}
Fix: Enforce ownership in the lookup or via policy.
{
order = db.orders.findById(path.id)
if (order.userId != auth.userId) return 403
return order
}
2) Unrestricted Resource Consumption in search
Vulnerable: GET /api/search?q=shoes returns all matches with no pagination or rate limits.
{
results = search(q) // returns thousands
return results // Large payload + can be spammed
}
Fix: Add pagination, caps, and rate limits.
{
limit = clamp(query.limit, 1, 50)
offset = clamp(query.offset, 0, 5000)
results = search(q, limit, offset)
headers = {"X-RateLimit-Limit": 100, "X-RateLimit-Remaining": calc()}
return { headers, body: results }
}
3) Broken Function Level Authorization (admin actions)
Vulnerable: POST /api/users/ban checks only that the user is logged in.
{
if (!auth.user) return 401
banUser(body.userId) // Any logged-in user can ban
return 204
}
Fix: Enforce role/permission checks.
{
if (!auth.user) return 401
if (!auth.user.roles.includes('admin')) return 403
banUser(body.userId)
return 204
}
4) SSRF in image fetcher
Vulnerable: POST /api/fetch-image downloads any URL provided by the client.
{
// Attacker can fetch http://169.254.169.254/latest/meta-data
img = http.get(body.url)
return store(img)
}
Fix: Enforce allowlists and block internal IP ranges; do HEAD first; set timeouts and size limits.
{
if (!isAllowedDomain(body.url)) return 400
if (isPrivateIP(resolve(body.url))) return 400
img = safeHttpGet(body.url, {timeout: 3s, maxSize: 2MB})
return store(img)
}
How to detect issues quickly (checklist)
- For every object read/update endpoint, verify ownership or explicit access policy.
- For every admin/premium route, enforce role checks server-side.
- Add pagination defaults and maximums to list/search endpoints.
- Set sensible rate limits per IP, per token, and per key action.
- Restrict server-side URL fetchers with allowlists and private IP blocking.
- Return minimal error details in production; enable security headers.
- Maintain an API inventory: versions, owners, environments, and exposure.
- Validate and sanitize inputs; reject unexpected properties.
Exercises
Work through these, then take the Quick Test. The test is open to everyone; sign in to save results.
-
Exercise 1: Spot the risks
Given endpoints:
GET /api/users/{id}(returns full profile including email),POST /api/billing/charge(no role checks),GET /api/logs?limit=5000(no auth, huge limit). Identify the OWASP categories and one fix each. -
Exercise 2: Rate limiting and pagination plan
Propose limits for:
GET /api/search,POST /api/login,POST /api/orders. Include per-IP and per-user/token where it makes sense. -
Exercise 3: Property-level authorization
Design a rule allowing users to update their profile but forbidding changes to
rolesandisVerifiedunless admin.
Need a nudge?
- Think: object vs. property vs. function authorization.
- Search endpoints: default limit 20–50, cap hard maximum.
- Login: low burst, strict per-IP and per-account limits.
Common mistakes and self-check
- Mistake: Relying on client-side checks. Self-check: Remove the UI and call the API directly—does it still block you?
- Mistake: Only checking auth, not authorization. Self-check: Can a normal user call an admin route?
- Mistake: Returning entire objects. Self-check: Are we filtering fields server-side?
- Mistake: No inventory. Self-check: Can you list all public endpoints and owners in 5 minutes?
- Mistake: No resource limits. Self-check: What prevents 10k requests/min or a 100MB response?
Practical projects
- Harden a sample user service: add BOLA checks, property-level validation, and RBAC for admin routes.
- Build a "safely fetch URL" microservice with allowlist, private IP blocking, and size/time limits (defend against SSRF).
- Create an API inventory file (YAML/JSON) listing each endpoint, auth type, owner, and risk notes.
Learning path
- Memorize the 10 categories and their signatures.
- Apply quick wins: BOLA checks, RBAC, pagination, rate limits.
- Add SSRF defenses and production-safe configurations.
- Establish API inventory and deprecate unknown/legacy endpoints.
- Automate: lint specs, add auth tests, and monitor rate limits.
Next steps
- Finish the exercises below, then take the Quick Test.
- Pick one practical project and implement it this week.
- Schedule a small security review for your current API.
Mini challenge
You inherit an API with GET /api/reports/{reportId}, POST /api/admin/export, and POST /api/fetch (downloads a URL). In one hour, list likely Top 10 risks for each endpoint and the first mitigation you would apply. Keep it concise and actionable.
Exercise solutions
Exercise 1 solution
Users endpoint: Excessive fields imply API3 (BOPLA). Fix: server-side field filtering based on role; only return allowed properties.
Billing charge: Missing role checks imply API5 (BFLA). Fix: enforce role/permission for billing operations.
Logs with huge limit and no auth: API4 (Unrestricted Resource Consumption) and API2/API8 issues. Fix: require auth, add pagination caps (e.g., max 100), rate limit.
Exercise 2 solution
- GET /api/search: default limit 20, max 50; rate 60/min per IP, 120/min per token.
- POST /api/login: 5/min per IP, 3/min per account; exponential backoff.
- POST /api/orders: 30/min per token; queue heavy work; idempotency keys.
Exercise 3 solution
{
// Pseudocode policy
allowedUpdates = ['name','avatarUrl','bio']
if (!user.roles.includes('admin')) {
forbid if body contains ['roles','isVerified']
}
validate body only has allowed fields (or admin-only extras)
}