OWASP API Top 10 Explained: BOLA, SSRF and More
Learn the OWASP API Security Top 10 (2023) with interactive labs: BOLA enumeration, mass assignment, SSRF, scalper bots, STRIDE and authorization test…
The OWASP API Security Top 10: common questions
Do random UUIDs prevent BOLA?
No. They make guessing IDs impractical, but IDs leak through links, logs and list endpoints. Only an ownership check scoped to the caller from the token fixes BOLA.
How do you prevent SSRF in an API that fetches URLs?
Allow-list destinations, resolve DNS and block private, loopback and link-local IPs, refuse redirects or re-check each hop, and run fetchers in an isolated egress network.
Related chapters
- Start here in System Design
- How a system design interview is graded
- The delivery framework: five phases and a clock
- Numbers to know: what one machine can really do
- System Design Concepts: CAP, SQL vs NoSQL, Message Queues
- SQL vs NoSQL: How to Choose a Database (System Design)
- Traffic Jumps 10×: What Breaks First? (System Design)
- Proxy vs Load Balancer vs API Gateway (System Design)
- CAP Theorem Explained Simply, with Examples
- Why Use a Message Queue? System Design Explained
- Message Queue vs Pub/Sub vs Kafka: Differences
- Polling vs Long Polling vs SSE vs Webhooks (System Design)
- API design in System Design
- REST vs GraphQL vs gRPC: Choosing an API (System Design)
- API Goals Canvas: Design APIs from the Consumer
- REST API Design: Resources, Methods, Status Codes
- HTTP Semantics, Richardson Maturity Model and HATEOAS
- GraphQL Explained: Over-fetching, Schema, N+1 Problem
- RPC and gRPC Explained: Protobuf, HTTP/2, When to Use
- API Design Best Practices: 9 Principles with Examples
- API Payload Design: Names, Types, Links and Errors
- Mapping Database Tables to API Resources, Step by Step
- API Design for Complex Schemas: Trees, Polymorphism, Bookings
- API Pagination, Idempotency Keys, Errors and Versioning
- Network-Efficient APIs: HTTP Caching, ETags, Field Selection
- Async APIs: 202 Accepted, Webhooks and Bulk Operations
- API Versioning and Evolution Without Breaking Clients
- API Contracts: OpenAPI, Contract Testing and Docs
- API Security: Authentication, Authorization, Rate Limiting
- OAuth 2, OpenID Connect and Scopes for APIs
- Running APIs: Gateways, Mesh, Rate Limits, Canaries
- API Troubleshooting Interview: Slow APIs and Unexpected Errors
- API Design Review: A Staff-Level Capstone
- Spotify Web API: Resources, IDs, Payloads and Paging
- Spotify Web API: OAuth PKCE, Scopes, Quotas, Rate Limits
- Spotify Web API Changes: Real API Evolution Lessons
- Database-heavy designs in System Design
- Design a Distributed Key-Value Store (System Design Interview)
- Design a Hotel Reservation System (System Design Interview)
- Design a Digital Wallet (System Design Interview)
- Design S3-like Object Storage (System Design Interview)
- Games in System Design
- Shard City: Learn Database Sharding by Playing a Game
- System Design Case Studies: Flash Sale, Chat, ChatGPT
- Flash Sale System Design: Who Gets the Last Item
- Food Delivery App System Design: Which Database?
- Cache Stampede: Why the Database Fell Over and the Fix
- Live Streaming System Design: Scaling for a Final
- ChatGPT System Design: Streaming LLM Responses
- RAG System Design: Product Q&A on Sale Day
- IRCTC Tatkal System Design: Booking Under Extreme Load
- Group Chat System Design: WebSockets and Fan-Out
- Authentication in System Design: Sessions, JWT, OAuth
- Authentication Methods Compared: Sessions, JWT, OAuth
- JWT in System Design: Sessions, Revocation, Refresh Tokens
- JWT Interview Questions and Answers
- Life of a session, step by step
- JWT Structure Explained: Header, Payload, Signature
- Forge lab: why verification must be strict
- JWT vs Session Authentication: Which to Use
- The validation pipeline: where a bad request dies
- How to Revoke a JWT: Blocklists and Short Expiry
- Refresh Tokens and Rotation Explained
- Logout in depth: one device, all devices, SSO
- JWT Security Best Practices
- OAuth 2.0 and OpenID Connect Explained
- Where to Store JWT: localStorage vs HttpOnly Cookie
- JWT in Microservices: API Gateway vs Service Validation
- Microservices, keys, and trust boundaries
- Sender-constrained tokens: making a stolen token useless
- Authentication vs Authorization Middleware
- Failure explorer: what breaks when X fails
- Capacity planner: sessions vs JWT vs hybrid
- Edge cases catalog
- Test it, measure it, migrate to it
- Anti-patterns to reject in a design review
- Design checklist and cheat sheet
Keep learning
Overview
Not found No such booking (a guess never lands) Server defences Random, unguessable IDs Run the script with John’s valid token 0 leaked Fixed. Every lookup filters by the caller, so another person’s booking does not exist for John. Returning 404 instead of 403 also hides whether the ID is real. Slowed, not fixed. Nobody guesses a random 12-character ID, but IDs leak: a shared ticket link, a support screenshot, a list endpoint, a log line. Any leaked ID still opens Priya’s booking, because the server never asks whose it is. Breached. John is logged in, so authentication passes, and the server returns whatever row the URL names. Counting upward walks the whole table: names, phone numbers, seats. Change my name Sneak in a role Top up the wallet Lab: John edits his profile, and adds a field the form never showed Allow-list writable fields: name, phone 422 Unprocessable Content A normal edit. Both versions of the server accept it. Accepted. The handler copied every key of the body onto the user row, so the database now holds what John typed. Real poster an image on the venue’s CDN Cloud metadata Internal admin a private service with no login Name that hides an IP its DNS record points to 169.254.169.254 Redirect from an allowed host an open redirect answers 302 to 169.254.169.254 Fetched and stored. Good URLs keep working with every defence on. Blocked. The fetcher refuses 3xx answers, so the second hop never happens. Blocked. The host is not on the short list of image hosts, so no request leaves the server. Blocked. The server resolved the name first and refused a private, loopback or link-local address. Leaked. The poster service sits inside the network, so it reached an admin page the internet never could, and saved the answer as “the poster”. Leaked. The server asked the cloud metadata endpoint for its own details and stored the reply where Asha can download it. On some setups that reply includes temporary cloud credentials. Lab: Asha’s venue app lets her import an event poster from a URL The URL in the request Defences on the fetcher Allow-list of image hosts Block private and link-local IPs after DNS Do not follow redirects Server-side fetch leaked
The map: ten ways an API fails
OWASP, an open security community, publishes a ranked list of API risks built from real incidents. The 2023 edition is the current one. Read it as a checklist for every endpoint you design: Broken Object Level Authorization John reads /bookings/1043, which is Priya’s. The server checked who he is, not whose booking it is. Broken Authentication Login and OTP endpoints that allow unlimited guesses, or tokens accepted without full validation. Broken Object Property Level Authorization Right object, wrong fields: writing role or wallet_balance (mass assignment), or reading password_hash (excessive data exposure). Unrestricted Resource Consumption No caps on page size, upload size, query cost, time, or paid side effects such as SMS. Broken Function Level Authorization A customer calls an admin route like POST /admin/events/123/refund-all and it works. Unrestricted Access to Sensitive Business Flows Every request is valid, but bots run the flow faster and wider than any human: scalpers buy every seat. Server Side Request Forgery The API fetches a URL the user supplied, and the user points it at your internal network. Security Misconfiguration Loose CORS, stack traces in errors, missing TLS, default passwords on admin consoles. Improper Inventory Management A forgotten /v1 or staging host still answers, without the fixes the main API got. Unsafe Consumption of APIs Trusting what a partner API returns as if it were your own validated data. Three of the ten (API1, API3, API5) are about authorization. That is the pattern to remember: identity is the easy half, and mostly solves it. Deciding what each identity may touch is where APIs leak.
API1, BOLA: the ID in the URL is a question, not a key
Random IDs (UUIDs or long random strings) are still worth using: they stop counting attacks and stop competitors reading your booking volume from the numbers. But they are defence in depth, not access control. The security chapter’s
API3: the right object, the wrong fields
The caller may own the object and still must not touch every property. This risk has two directions: writing fields they should not set, and reading fields they should not see. Mass assignment comes from convenient code that copies the request body straight onto a model. The fix is a separate input schema per operation that lists only writable fields and rejects anything else. Server-owned fields (role, wallet_balance, status, price) never appear in an input schema at all. Excessive data exposure GET /events/123/attendees returns the full user rows, and the venue app shows only names. Anyone reading the raw response sees email, phone, password_hash, internal_notes. Hiding fields in the client is not hiding them. Response shaped on the server An output schema per audience: Asha’s attendee list gets name and seat. The serializer builds the response from that list, so a new database column is private until someone adds it on purpose. The Design of Web APIs makes the same point from the design side: decide per consumer which data is sensitive before you name the fields, and mask what must be shown partly, like +91 98xxx 40112.
API2 and API5: weak front doors and unlocked back rooms
Token present and valid? signature, expiry, issuer, audience Function allowed? role may call this route Object allowed? this booking is yours Fields allowed? you may write name, not role Four gates per request. API2 breaks the first, API5 the second, API1 the third, API3 the fourth. Broken authentication is rarely a broken algorithm. It is a login or OTP endpoint with no attempt limit (credential stuffing tries leaked passwords from other sites), a password reset that reveals whether an email is registered, or a token check that skips the expiry or audience. The OAuth chapter BFLA is BOLA one level up: John cannot see the admin screen, but the route behind it answers anyone with a valid token. Hiding a button is not a permission. Put admin routes under their own prefix, deny by default, and require an explicit role for each route, checked in the service and not only at the gateway. \n\n403 Forbidden
API4 and API6: too much, and too fast for a human
Resource consumption is about cost per request. A single valid call can be expensive, so each expensive dimension needs its own cap, not only a request count: cap on the ticket site what it stops page size limit at most 100 ?limit=1000000 dumping the events table request body 1 MB JSON, rejected at the gateway huge payloads eating memory while parsing posters up to 5 MB, image types only filling storage, decompression bombs query cost GraphQL depth and complexity limits one nested query joining everything ( 10 s server timeout, 2 s on downstream calls slow requests piling up and holding threads paid side effects 3 OTP SMS per phone per hour, plus per IP SMS spam that costs you ₹ per message and harasses a stranger Sensitive business flows are harder: every single request is legitimate. When the Coldplay show at event 123 goes on sale, a scalper runs 500 accounts from 500 home IPs and holds every seat in seconds. A per-user rate limit never fires, because each account sends only a few requests. 1 account, very fast 500 accounts, 500 IPs bots driving real browsers rate limit per user rate limit per IP cap of 4 seats per event per verified phone and card device fingerprinting and velocity checks virtual queue with random admission at sale start CAPTCHA at the hold-seats step No single control wins. Layer them on the one flow that matters: holding and paying for seats. The design lesson: find the flows a business could lose money on (seat holds, referral credits, coupon codes, OTP sends) during design review, and give each one limits that match how a real fan behaves, not a generic number.
API7, SSRF: when your server fetches for the attacker
Allow-list destinations when the feature permits it (known CDNs, the partner’s domain). Otherwise resolve the name, then check the IP: refuse private, loopback and link-local ranges (including IPv6), and connect to the IP you checked so DNS cannot change its answer in between. No redirects, or re-run every check on each hop. Run fetchers in a separate egress network with no route to internal services, and lock down the metadata service (on AWS, require IMDSv2 session tokens).
API8 to API10: settings, forgotten hosts and partners
Improper inventory Unsafe consumption The payment partner redirects John back with ?status=paid, and the site marks the booking paid. Anyone can type that URL. Misconfiguration is fixed by defaults in code, not by memory: a hardened gateway template, error bodies that carry a code and a request ID but no internals ( error design ), CORS set to an explicit list of your web origins. Inventory is fixed by knowing every host and version that answers: generate the list from the gateway’s routes and the OpenAPI contracts , compare it with what traffic logs actually see, and switch off old versions on the date you announced ( Trust the redirect Verify with the partner Treat the redirect as a hint. Confirm by a signed webhook or a server-to-server call, and check the amount (₹5,000), currency and booking ID match before marking it paid. The general rule for API10: data from a partner is input. Validate its shape, escape it before it reaches SQL or HTML, set timeouts, and use TLS for every call.
Security by design: threat-model it, then test it
Zero trust no internal API Every service checks the token and the permission, even behind the gateway. “Only our app calls it” lasts until one service is compromised or one route is exposed by mistake. Validate everything strict schemas Unpredictable identifiers no counting Random IDs for bookings, payments and users, so enumeration and volume leaks need more than a for loop. Safe user flows server owns the state The server computes the price, holds seats with an expiry, and moves a booking from held to paid only after the payment is verified. A threat model asks “what could go wrong?” before code exists. STRIDE gives six prompts. Here they are for the busiest endpoint, POST /events/123/bookings: what it looks like here design answer a stolen token books as John short-lived tokens, step-up check before payment body sends "price": 1 “I never booked that” audit log with user, request ID and idempotency key Information disclosure “seat A12 is held by priya@…” in an error generic errors: “seat not available” Denial of service bots hold every seat, never pay holds expire in 10 minutes, cap holds per user and event Elevation of privilege body sends "user_id": "u_priya" user taken from the token only Each row becomes a test. The cheapest, highest-value suite is an authorization matrix: every role, against every endpoint, on an object they own and one they don’t, with the expected status. John, own booking John, Priya’s booking Asha (venue) no token PATCH /users/me with role PATCH /events/123/tickets POST /admin/events/123/refund-all Generated from the route list, run in CI on every change. A new route with no row fails the build. Two more layers catch what the matrix misses. Fuzzing tools (Schemathesis is one) read the OpenAPI document and send thousands of odd inputs: nulls, huge numbers, unknown fields, wrong types. Contract tests check that responses match the output schema, so a leaked column breaks the build ( contracts chapter ). Business-flow tests come from the threat model: book 5 seats when the cap is 4, reuse a one-time coupon.
The interview line
the next chapter Spotify scopes every personal endpoint to /me, so there is no user ID to swap, and dropped other users’ profiles for development-mode apps in 2026. Data minimisation on Spotify Its redirect URI rules stop code interception. Redirect URI rules
More courses
- Jobs, linked to lessons — Browse jobs where every requirement links to the chapter that teaches it. See how ready you are, how long the gap takes to close, and which listings look stale.
- Match a job post to courses — Paste a job post. Each skill is matched to a track here and put in learning order, foundations first.
- React from open source with patterns — Patterns extracted from Material UI, Radix, React Aria, React Hook Form, Redux Toolkit, TanStack, and Next.js — not slides about hooks.
- Database in depth — How a query becomes work. Indexes, isolation, locks, and the plans that explain why a page is slow — not an ORM tour.
- Build a real API with NestJS — One ticket-booking API, built in order: a plain Node server first, then NestJS modules, validation, a real database, and the features real clients lean on.
- Microservices — Microservice patterns one diagram at a time: how to split a monolith, how services talk, and how data stays consistent without distributed transactions.
- Kafka — A beginner-to-senior path through event logs, reliable delivery, production operations, and disaster recovery.
- AI engineering for software engineers — Eight free reads, in order, for software engineers moving into AI: when to build an agent, how to build one, how to test it, and how the model works underneath.
- Frontend system design — How a UI is sliced when it is a product, not a page: ownership of state, cache, routes, and what happens when the network lies.
- TypeScript in depth — The type system as a design tool. Inference, narrowing, generics, and the errors that mean the model is wrong — not a catalog of utility types.
- CSS in depth — See the box, then the page. Visual chapters from the box model through Flexbox surprises, Grid, selectors, and units, then backgrounds, shapes, and visual effects, each with a playground.
- Accessibility — Foundations first (WCAG in plain words, semantic HTML, lists, alt text), then hands-on HTML labs, a full pass of WAI-ARIA 1.3, and a final fix-the-page challenge.
- Docker — What an image is, why layers cache, and how containers find each other on a network.
- Redis — Fifteen Redis patterns, one picture each: cache, session, lock, counter, rate limiter, queue, and when Redis is the wrong tool.
- Kubernetes — Kubernetes from zero to operators: why it exists, Pods, Deployments and Services, then configuration, networking, storage, scheduling, scaling and security.