Safeguard
Security Guides

REST API Security Best Practices: The OWASP API Top 10 in Practice

Most API breaches aren't exotic — they're broken object-level authorization and missing rate limits. A practical walk through the OWASP API Security Top 10.

Daniel Osei
Security Researcher
5 min read

The uncomfortable truth about API breaches is that they are rarely clever. They are almost always an endpoint that returns a record without checking whether the caller is allowed to see it, or an API with no rate limit that an attacker scripts against for hours. The OWASP API Security Top 10 (2023) codified this: the top two categories are broken object-level authorization and broken authentication — the boring, fundamental failures. This guide walks the most impactful items in that list with the concrete code and configuration that closes each one, so your REST API fails safe under the attacks that actually happen.

Why is broken object-level authorization (BOLA) the #1 API risk?

Because it is trivial to introduce and invisible in a happy-path test. BOLA (API1:2023) happens when an endpoint uses an ID from the request to fetch a resource but never verifies the authenticated user is entitled to that specific resource. GET /api/invoices/1043 returns invoice 1043 — but does it check that 1043 belongs to the caller? If not, incrementing the ID walks the whole table.

// VULNERABLE: returns any invoice by ID
app.get("/api/invoices/:id", auth, async (req, res) => {
  const invoice = await db.invoice.findById(req.params.id);
  res.json(invoice);
});

// SAFE: scope the lookup to the caller
app.get("/api/invoices/:id", auth, async (req, res) => {
  const invoice = await db.invoice.findOne({
    _id: req.params.id,
    ownerId: req.user.id, // object-level check
  });
  if (!invoice) return res.sendStatus(404);
  res.json(invoice);
});

Enforce ownership on every single object lookup. Never trust a client-supplied ID to imply authorization, and don't rely on unguessable IDs (UUIDs) as a substitute for a real check.

What does broken authentication look like in practice?

Missing or weak verification of who is calling (API2:2023). The recurring mistakes: accepting tokens without validating signature and expiry, no lockout or throttling on login endpoints, long-lived tokens that never rotate, and secrets in URLs (which land in logs and browser history). Validate JWTs fully — signature, exp, iss, aud — reject alg: none, keep access tokens short-lived with refresh rotation, and rate-limit authentication endpoints aggressively to blunt credential stuffing.

How does mass assignment sneak into an API?

By binding a request body straight onto a database object (API3/API6 territory). If PATCH /api/users/me does User.update(req.body), a client can add "role": "admin" to the payload and escalate. Bind only an explicit allowlist of fields:

// SAFE: allowlist the fields a client may set
const allowed = ["name", "email", "bio"];
const updates = Object.fromEntries(
  Object.entries(req.body).filter(([k]) => allowed.includes(k))
);
await db.user.update({ _id: req.user.id }, updates);

The mirror problem is excessive data exposure — returning the whole object including passwordHash or internal flags. Shape responses to only the fields the client needs; don't lean on the frontend to hide sensitive fields.

What stops an API from being ground into dust?

Rate limiting and resource quotas (API4:2023, unrestricted resource consumption). Without them, an attacker scripts your endpoints for scraping, brute force, or plain denial of service — and pagination without a max page size lets a single ?limit=1000000 exhaust memory. Apply per-client rate limits, cap page sizes, set request-body size limits, and timeout expensive operations.

import rateLimit from "express-rate-limit";

app.use("/api/", rateLimit({
  windowMs: 60_000,
  max: 100,             // 100 requests/minute per client
  standardHeaders: true,
}));

Which platform-level defenses round out the list?

  • Function-level authorization (API5): don't just hide admin endpoints in the UI — check roles server-side on every privileged route. Broken function-level authorization is calling DELETE /api/admin/users/5 as a normal user and having it work.
  • Input validation: validate every input against a schema; parameterize all database queries to prevent injection.
  • Transport + headers: TLS everywhere, HSTS, X-Content-Type-Options: nosniff, and a tightly scoped CORS policy — never reflect arbitrary Origin with credentials.
  • Inventory (API9): you can't secure endpoints you forgot exist; keep an accurate inventory and retire old, unversioned, and staging endpoints.

Because most of these are runtime behaviors, a dynamic scan that authenticates and exercises your endpoints is the most reliable way to confirm authorization and rate limits actually hold.

OWASP API Top 10 quick map

Risk (2023)Defense
API1 BOLAOwnership check on every object lookup
API2 Broken authenticationFull token validation, throttling, short TTLs
API3 Broken property-level authzAllowlist writable + returned fields
API4 Resource consumptionRate limits, page caps, timeouts
API5 Broken function-level authzServer-side role checks
API8 MisconfigurationTLS, headers, strict CORS
API9 InventoryRetire and version endpoints
API10 Unsafe consumptionValidate upstream/3rd-party data

The dependency dimension

Your API framework, auth libraries, and serializers are all dependencies, and dependency risk is API risk — the 2025 npm attacks (Shai-Hulud across 500+ packages per CISA; trojanized chalk/debug) prove a poisoned library needs no coding mistake to breach you. Pair the controls above with software composition analysis across the full dependency graph and a scan gate wired into CI.

How Safeguard Helps

Safeguard resolves your API's complete dependency tree and uses reachability analysis to focus you on the CVEs your endpoints actually invoke. Griffin, our AI engine, reviews new package versions for behavioral anomalies, and auto-fix opens tested pull requests with the minimal safe upgrade. The authorization and rate-limiting work stays yours — Safeguard secures the libraries beneath it and helps you verify the whole thing in CI.

Secure your REST API end to end — create a free account, read the documentation, or see how Safeguard compares to Veracode.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.