arrow_backBACK TO SECURITY ENGINEERING FOR BACKEND ENGINEERS — THREATS, AUTH, CRYPTO AND SHIPPING SECURELY
Lesson 08Security Engineering for Backend Engineers — Threats, Auth, Crypto and Shipping Securely6 min read

API Security

October 28, 2026

TL;DR

APIs are where most of your attack surface lives now, and the OWASP API Security Top 10 captures where they actually get breached. The everyday ones: BOLA (changing an id to read others' data — the #1 issue, Lesson 3), broken authentication (weak tokens, no expiry, no lockout), broken object property authorization / mass assignment (the API binds request fields straight to your model, so an attacker adds is_admin:true), unrestricted resource consumption (no rate limit, so brute-force and bill-shock), and security misconfiguration (debug on, verbose errors, wide-open CORS). Authorization bugs dominate real breaches, so check WHO may touch WHAT on every call and allowlist which fields a request may set. Rate limiting is a token bucket: each request spends a token, an empty bucket returns 429, the bucket refills steadily — apply it hardest to login/OTP/reset endpoints (with lockout and a slow hash), per-user AND per-IP, and to expensive endpoints to protect cost and availability.

Modern backends are mostly APIs — JSON over HTTP, consumed by web apps, mobile apps, and other services. That makes the API layer where most of your attack surface lives, and where most of the lessons so far come together. OWASP maintains a dedicated API Security Top 10 precisely because API breaches have their own characteristic shape, and it’s a genuinely useful map of where things go wrong. This lesson walks the everyday risks from that list and then digs into rate limiting, the one defensive mechanism that deserves its own explanation.

The API Risks That Actually Get You

You don’t need to memorise all ten items; you need to recognise the handful that cause the real incidents. Here they are, with the mistake and the fix.

The API risks that actually get you, drawn from the OWASP API Security Top 10, the everyday ones. Broken Object Level Authz (BOLA): risk, changing an id to read others' data; fix, scope every query by owner/tenant. Broken authentication: risk, weak tokens no expiry no lockout; fix, strong auth short tokens rate-limit login. Broken Object Property Authz: risk, over-posting / mass assignment; fix, allowlist writable fields explicitly. Unrestricted resource consumption: risk, no rate limit leading to brute-force / bill shock; fix, rate limits quotas pagination caps. Security misconfiguration: risk, debug on verbose errors open CORS; fix, harden defaults minimal CORS no stack traces. Authorization bugs dominate real API breaches — check WHO may touch WHAT on every call
  • Broken Object Level Authorization (BOLA) — the number-one API risk, covered in depth in Lesson 3. Changing an id in the URL to access another user’s object, because the server checked authentication but not ownership. Fix: scope every object access by owner/tenant, on read, write, and delete.
  • Broken authentication — weak or missing auth: guessable tokens, tokens that never expire, no lockout or rate limit on login, accepting alg:none JWTs (Lesson 2). Fix: strong auth, short-lived tokens, rate-limited and lockout-protected login, pinned JWT algorithms.
  • Broken Object Property Authorization / mass assignment — the one worth a closer look (below). The API lets a user write fields they shouldn’t, like flipping their own is_admin. Fix: explicitly allowlist which fields a request may set.
  • Unrestricted resource consumption — no rate limits or quotas, so an attacker can brute-force credentials, scrape all your data, or run up a huge cloud bill (the “denial of wallet” variant). Fix: rate limits, quotas, pagination caps, timeouts (below).
  • Security misconfiguration — debug mode on in production, verbose stack traces leaking internals, overly permissive CORS (Access-Control-Allow-Origin: * with credentials), missing security headers (Lesson 5). Fix: harden defaults, return generic errors, lock CORS down to the origins that actually need it.

The thread running through the top of this list is unmistakable: authorization bugs dominate real API breaches. Not fancy exploits — forgotten checks. The single most valuable habit for API security is the one from Lesson 3: on every endpoint, check who may touch what.

Mass Assignment: The Convenience Trap

Mass assignment deserves special attention because the very framework feature that makes you productive is what creates the bug. Many frameworks let you bind an incoming request body straight to a model object in one line — user.update(request.body) — which is wonderfully convenient and quietly dangerous. If the request includes fields you didn’t intend to expose, they get written too.

The illustrative scenario (made up to show the mechanism): your “update profile” endpoint expects {name, bio}. An attacker sends {name, bio, is_admin: true, account_balance: 999999}. If your code binds the whole body to the user model, you just handed them admin and a fortune. The attacker learned the extra field names by reading your API responses, your mobile app, or just guessing common ones.

The fix is to explicitly allowlist the fields a request may set, never bind the raw body wholesale. Accept only name and bio; ignore everything else. Concretely: use a strict input schema / DTO that lists exactly the writable fields, or your framework’s “permit these params” mechanism. Sensitive fields (is_admin, role, verified, balance, owner_id) should only ever be settable through dedicated, authorized code paths — never through a general update endpoint. This is really the property-level sibling of BOLA: just as you check which objects a user may touch, you must control which fields of an object a user may write.

Rate Limiting: A Token Bucket

Rate limiting shows up repeatedly in this course — as brute-force defense (Lesson 2), DoS defense (Lesson 1), and resource-consumption defense (above) — so it’s worth understanding the mechanism. The standard model is a token bucket, and it’s pleasingly simple.

Rate limiting equals a token bucket: the simple mechanism behind brute-force and abuse defense. The bucket holds tokens up to a capacity (the burst size) and refills at a steady rate. Each request takes 1 token: token available means allow (200); bucket empty means reject (429); bucket refills over time so it's fair but bursts are capped. Where it matters: login / OTP / password-reset endpoints are the front line against credential stuffing, combine with account lockout / exponential backoff and a slow password hash (Lesson 2); per-user AND per-IP limits (an attacker rotates IPs, a logged-in abuser doesn't); expensive endpoints (search, export, LLM calls) to protect cost and availability; return 429 with Retry-After and limit at the edge (gateway) so bad traffic never hits your app

Picture a bucket that holds up to N tokens (the burst capacity) and refills at a steady rate (say, 10 tokens per second). Each request takes one token: if a token is available, the request is allowed (200); if the bucket is empty, the request is rejected (429 Too Many Requests). Because the bucket refills over time, legitimate users with normal bursts sail through, while anyone hammering the endpoint quickly drains it and gets throttled. The two knobs — bucket size (how big a burst you tolerate) and refill rate (sustained throughput) — let you tune it per endpoint.

Where it matters most:

  • Login, OTP, and password-reset endpoints — the front line against credential stuffing and brute-force. As Lesson 2 showed, a slow password hash already limits guessing; rate limiting plus account lockout / exponential backoff closes the gap. These endpoints deserve the tightest limits.
  • Per-user and per-IP limits. An attacker rotates IP addresses to dodge IP limits, so also limit per account/token. A logged-in abuser doesn’t change identity, so per-user limits catch them. You want both.
  • Expensive endpoints — search, export, report generation, LLM calls, anything that costs real compute or money. Rate limits here protect both availability and your bill.
  • Return 429 with a Retry-After header, and limit at the edge (API gateway / load balancer) where you can, so abusive traffic is dropped before it ever reaches your application and consumes resources.

Rate limiting is one of those controls that quietly prevents a whole category of attacks, costs little, and is easy to add — a gateway or a small middleware with a shared counter (often in Redis) gets you most of the way.

A Few More API Habits

  • Validate and type-check every input against a schema (Lesson 4). Reject unexpected shapes early.
  • Don’t leak internals in errors. A 500 should say “something went wrong,” not print a stack trace and SQL. Log the detail server-side; return a generic message and a request id.
  • Lock down CORS. Only the origins that genuinely need cross-origin access should be allowed, and be very careful combining Allow-Credentials: true with broad origins.
  • Version and deprecate carefully. Old, unmaintained API versions are a classic soft underbelly — an attacker uses v1 because v1 never got the fix v2 did.
  • Authenticate service-to-service calls too. Internal APIs aren’t safe just because they’re “internal” (Lesson 10, Lesson 5’s SSRF). Require auth between services.

Pitfalls to Avoid

  1. Missing object-level authorization (BOLA). The top API breach cause. Check ownership on every object access (Lesson 3).
  2. Mass assignment. Binding the raw request body to your model lets attackers set is_admin. Allowlist writable fields explicitly.
  3. No rate limiting. Invites brute-force, scraping, and bill-shock. Add token-bucket limits, tightest on auth and expensive endpoints.
  4. Only IP-based limits. Attackers rotate IPs. Limit per-user/token as well.
  5. Verbose errors and debug mode in production. They leak internals. Return generic errors with a request id; log detail server-side.
  6. Permissive CORS. * with credentials is dangerous. Allow only the origins that need it.
  7. Forgotten old API versions and unauthenticated internal APIs. Deprecate old versions; authenticate service-to-service calls.

Quick Recap

  • APIs are most of your attack surface; the OWASP API Security Top 10 maps where they break.
  • Authorization bugs dominate: BOLA (object ownership) and broken object-property authz (mass assignment) are the big ones — check who may touch what, and allowlist which fields a request may set.
  • Guard against broken authentication, unrestricted consumption, and misconfiguration (short tokens, quotas, generic errors, tight CORS).
  • Rate limiting is a token bucket: spend a token per request, 429 when empty, refill steadily. Apply it hardest to auth and expensive endpoints, per-user and per-IP, at the edge.

Next lesson: your code is mostly other people’s code. We’ll look at supply-chain security — dependencies, SBOMs, and hardening the CI/CD pipeline that turns your code into a running artifact.