Security

API Security in 2026: The OWASP API Top 10, With Actual Fixes

Layered diagram showing API security controls from network edge through gateway, authentication, authorization and data layer

A note on versions before we start, because there is a lot of confused content on this topic. People search for "OWASP API Security Top 10 2026," but the most recent official release of that list is the 2023 edition, and as of mid-2026 it remains the current standard. Anyone selling you a "2026 list" is repackaging the 2023 one. That is fine, the list is still accurate, but you should know what you are reading.

What follows is that list, each item paired with the fix we would actually implement. No compliance theatre.

API1: Broken Object Level Authorization

This is the big one. It causes more real-world API breaches than the other nine combined, and it is embarrassingly simple.

GET /api/invoices/8891   → your invoice
GET /api/invoices/8892   → somebody else's invoice

The endpoint authenticated the user, then fetched the object by ID, then returned it. It never asked whether this user is allowed to see that object.

The fix is to make authorization impossible to forget. Not to remember it more carefully.

// Fragile: correct today, broken the first time someone adds an endpoint in a hurry
const invoice = await db.invoice.findUnique({ where: { id } })
return invoice
 
// Durable: ownership is part of the query, not a separate step
const invoice = await db.invoice.findFirst({
  where: { id, tenantId: ctx.tenantId }
})
if (!invoice) return notFound()  // note: 404, not 403

Two details that matter. First, scope the query rather than fetching-then-checking, so there is no path where the check gets skipped. Second, return 404 rather than 403 for objects the caller does not own. A 403 confirms the object exists, which is itself a leak that lets an attacker enumerate your ID space.

If you run a multi-tenant product, database-enforced row-level security makes this class of bug structurally impossible. We cover that setup in multi-tenant SaaS architecture.

API2: Broken Authentication

Weak or missing authentication on the endpoints that establish identity. Login, password reset, token refresh.

The things we check for on every audit:

  • No rate limiting on login. Credential stuffing needs nothing more than a wordlist and patience. Rate limit per IP and per account, because attackers rotate IPs.

  • Password reset tokens that are guessable or long-lived. Use a cryptographically random token, hash it in storage, expire it in fifteen minutes, and invalidate on use.

  • JWTs with no expiry, or with alg: none accepted. Always validate the algorithm explicitly against an allowlist. Never let the token tell you how to verify itself.

  • Refresh tokens that never rotate. Rotate on every use and detect reuse. A replayed refresh token means the session is compromised, so kill the whole family.

jwt.verify(token, publicKey, {
  algorithms: ['RS256'],       // explicit allowlist, never read from the header
  issuer: 'https://api.example.com',
  audience: 'example-web',
  maxAge: '15m'
})

API3: Broken Object Property Level Authorization

Two failures in one category.

Excessive data exposure: the endpoint returns the whole database record and the client renders three fields. The other twenty, including passwordHash and internalRiskScore, are sitting in the response body for anyone who opens the network tab.

Mass assignment: the endpoint accepts a JSON body and spreads it into an update. A user sends {"name": "Zee", "role": "admin"} and promotes themselves.

Both are fixed by explicit schemas in both directions. Never trust object spread near a database.

// Inbound: allowlist what a user may set
const UpdateProfile = z.object({
  name: z.string().min(1).max(120),
  timezone: z.string(),
}).strict()   // .strict() rejects unknown keys instead of ignoring them
 
// Outbound: allowlist what leaves the building
const PublicUser = z.object({
  id: z.string(),
  name: z.string(),
  avatarUrl: z.string().nullable(),
})
return PublicUser.parse(user)

That .strict() is doing real work. Without it, unknown fields are silently dropped, which is safe, but you lose the signal that someone is probing you.

API4: Unrestricted Resource Consumption

An endpoint that lets a caller request anything at any size, any depth, any rate. This is both a denial of service vector and, in the cloud era, a way to run up your bill.

Controls worth having:

  • Rate limits per authenticated identity, not only per IP

  • A hard maximum on pagination limit, enforced server-side and never taken from the client unclamped

  • Query depth and complexity limits if you run GraphQL

  • Request body size caps at the gateway

  • Timeouts on every outbound call your API makes

const limit = Math.min(Number(req.query.limit) || 25, 100)  // clamp, never trust

One line. It stops ?limit=999999999 from becoming an incident.

API5: Broken Function Level Authorization

API1 was about objects. This is about operations. The /api/admin/users endpoint that checks you are logged in but never checks you are an admin.

This happens most often when the frontend hides the admin button and everyone assumes that is the control. It is not. Hiding a button hides nothing.

Fix: deny by default at the router level. Every route declares its required permission, and a route with no declaration fails closed rather than open.

route('/admin/users', {
  requires: 'users:admin',    // absent this key, the route returns 403
  handler: listUsers,
})

API6: Unrestricted Access to Sensitive Business Flows

Newer in the 2023 edition and increasingly relevant. Nothing is technically broken. The API is being used exactly as designed, at a scale and speed that harms the business.

Ticket scalping bots. Automated bulk purchases of limited stock. Scripted account creation to farm referral credit. Every request is valid. The aggregate is the attack.

Defending this is a product decision, not only an engineering one:

  • Identify which flows have real-world value if automated

  • Add friction proportional to that value, such as device fingerprinting, proof of work, or step-up verification

  • Monitor for behavioural patterns rather than individual bad requests

  • Accept that you are raising cost for attackers, not eliminating them

API7: Server Side Request Forgery

Your API accepts a URL from a user and fetches it. Webhook registration, image import from a link, PDF generation from a page.

The attacker gives you an internal address instead, and your server, sitting inside your network with credentials, obligingly fetches it. Cloud metadata endpoints are the classic target because they hand out IAM credentials.

Fix, layered:

  1. Allowlist schemes: https only

  2. Resolve DNS yourself, then validate the resulting IP against private ranges before connecting

  3. Block the cloud metadata address explicitly

  4. Disable redirect following, or re-validate after every hop

  5. Where the risk justifies it, route these fetches through an egress proxy on an isolated network segment

Step two matters more than it looks. Validating the hostname string is not enough, because DNS can resolve a public-looking name to 169.254.169.254.

API8: Security Misconfiguration

The broad category. In practice it is nearly always one of these:

  • CORS set to Access-Control-Allow-Origin: * alongside Allow-Credentials: true

  • Stack traces returned to clients in production

  • Debug endpoints or GraphQL introspection left enabled

  • Default credentials on an admin panel or an internal dashboard

  • Missing security headers: HSTS, CSP, X-Content-Type-Options

  • Cloud storage buckets set to public because it made a deploy work once

Fix: treat configuration as code, scan it in CI, and diff production against a known-good baseline on a schedule. A config change nobody reviewed is how most of these arrive.

API9: Improper Inventory Management

You cannot secure what you do not know exists. Almost every organization has:

  • A v1 API still running because one mobile client version never updated

  • A staging environment on the public internet with production-shaped data

  • An internal API that got exposed to make an integration work in 2023

  • Documentation describing endpoints that no longer match the code

Fix: maintain a real inventory. Generate the spec from the code so it cannot drift. Every endpoint has a named owner and a documented deprecation date. Non-production environments require authentication at the network edge, always.

API10: Unsafe Consumption of Third-Party APIs

You validate what your users send. Do you validate what your vendors send back?

Teams routinely apply strict validation to inbound user requests and then pipe a payment provider's webhook payload straight into the database because the vendor is trusted. That trust is misplaced. Vendors get breached, and vendor responses get intercepted.

Fix: treat every third-party response as untrusted input. Validate the schema, verify webhook signatures, enforce timeouts, and never follow redirects from a vendor endpoint without re-validating the destination.

The three habits that matter more than the list

If you only take three things from this:

Deny by default, everywhere. Routes without an explicit permission fail closed. Queries without a tenant scope return nothing. Fields not on an allowlist do not leave the server. Security that depends on someone remembering to add a check will eventually fail, because eventually someone will be shipping at 6pm on a Friday.

Push authorization down to the data layer. Application-level checks are one refactor away from being bypassed. Database-enforced row-level security survives refactors, new endpoints, and the intern's first pull request.

Log the security-relevant events, and actually look at them. Failed authorization attempts, permission changes, admin actions, unusual access patterns. Most breaches are detected weeks late not because logging was absent but because nobody was reading it.

Where to start if this list feels overwhelming

Do these four in order this week:

  1. Pick your three most sensitive endpoints. Try to access another account's data through them. Right now, in a terminal.

  2. Check whether any endpoint returns more fields than the UI displays.

  3. Confirm your login endpoint is rate limited per account, not only per IP.

  4. Grep for findUnique, findById or equivalent, and check every result is scoped by owner.

Those four cover the majority of what we find in real audits.

If you would rather have someone else look with fresh eyes, we do API security reviews. Fixed scope, plain-language report, and the remediation tracker above filled in for your codebase.

Common questions

What is the latest OWASP API Security Top 10?

The most recent official release is the 2023 edition, and it remains the current standard as of mid-2026. Despite many articles titled with later years, OWASP has not published a newer API-specific list. The 2023 edition added SSRF and unrestricted access to sensitive business flows to reflect attack patterns seen in the field.

What is the most common API vulnerability?

Broken Object Level Authorization, listed as API1. It causes more real-world API breaches than the other nine risks combined. It happens when an endpoint authenticates the user, fetches an object by ID, and returns it without checking whether that user is allowed to see that specific object. Scoping the database query by owner, rather than fetching then checking, removes the class of bug entirely.

Should an API return 403 or 404 for objects a user does not own?

Return 404. A 403 confirms the object exists, which lets an attacker enumerate your ID space and map your data even without reading it. Returning 404 makes an object the caller does not own indistinguishable from one that was never there.

How do I secure a REST API?

Work in layers. Rate limits and body size caps at the gateway, explicit JWT algorithm allowlists and rotating refresh tokens at the authentication layer, deny-by-default permissions at the routing layer, strict inbound and outbound schema validation at the application layer, and row-level security at the data layer. The data layer control matters most, because it is the only one that survives a refactor.


Keep reading

Frequently asked questions

What is the latest OWASP API Security Top 10?

The most recent official release is the 2023 edition, and it remains the current standard as of mid-2026. Despite many articles titled with later years, OWASP has not published a newer API-specific list. The 2023 edition added SSRF and unrestricted access to sensitive business flows to reflect attack patterns seen in the field.

What is the most common API vulnerability?

Broken Object Level Authorization, listed as API1. It causes more real-world API breaches than the other nine risks combined. It happens when an endpoint authenticates the user, fetches an object by ID, and returns it without checking whether that user is allowed to see that specific object. Scoping the database query by owner, rather than fetching then checking, removes the class of bug entirely.

Should an API return 403 or 404 for objects a user does not own?

Return 404. A 403 confirms the object exists, which lets an attacker enumerate your ID space and map your data even without reading it. Returning 404 makes an object the caller does not own indistinguishable from one that was never there.

How do I secure a REST API?

Work in layers. Rate limits and body size caps at the gateway, explicit JWT algorithm allowlists and rotating refresh tokens at the authentication layer, deny-by-default permissions at the routing layer, strict inbound and outbound schema validation at the application layer, and row-level security at the data layer. The data layer control matters most, because it is the only one that survives a refactor.

Keep reading

All articles