API Security Checklist: Mapped to the OWASP API Security Top 10
Ten categories, each with the concrete checks to run against your own endpoints, following the OWASP API Security Top 10 (2023 edition).
Updated
APIs fail differently from web pages. There is no UI hiding the fields you did not mean to expose, and every identifier in a URL or body is an input an attacker controls. The OWASP API Security Top 10 (2023) names the ten most common failures. Below, each one is turned into checks you can run against your own endpoints.
API1: Broken object level authorization
The most common API flaw: the endpoint verifies you are logged in but not that the record you asked for is yours. Changing /orders/1001 to /orders/1002 returns someone else's order.
- Every handler that takes an object ID checks ownership or permission for that object on the server.
- Use unguessable IDs as defense in depth, never as the control itself.
- Write a test per resource: user A requests user B's record and must get a 403 or 404.
- If you use row level security in the database, confirm it is enabled on every table that holds user data.
API2: Broken authentication
- Login, token refresh and password reset are rate limited and do not reveal whether an account exists.
- Tokens expire, can be revoked, and are validated for signature, issuer, audience and expiry on every request.
- Never accept the "none" algorithm, and pin the allowed signing algorithms.
- Credentials and tokens travel in headers, never in URLs, where they end up in logs.
- Sensitive actions such as changing email or password require recent re-authentication.
API3: Broken object property level authorization
Two failures live here: returning more fields than the caller should see, and accepting fields the caller should not set (mass assignment).
- Return explicit response shapes. Do not serialize a whole database row.
- Accept an allowlist of writable fields. A client must not be able to send role, isAdmin, plan or ownerId and have it stick.
- Strip internal fields such as password hashes, tokens and internal notes from every response path, including errors.
// Allowlist what a client may update instead of spreading the body.
const { displayName, timezone } = parsedBody
await db.update(users).set({ displayName, timezone }).where(eq(users.id, session.userId))API4: Unrestricted resource consumption
- Rate limit per user, per API key and per IP, with stricter limits on expensive endpoints.
- Cap request body size, page size, file upload size and query complexity.
- Put timeouts on every upstream call so one slow dependency cannot exhaust your workers.
- If an endpoint triggers paid work such as SMS, email or AI calls, add a quota or spend limit, not only a rate limit.
API5: Broken function level authorization
- Admin and internal routes enforce a role check in the handler, not only a hidden link in the UI.
- Deny by default: new routes require an explicit permission rather than inheriting open access.
- Test every route with a low-privilege account and with no account at all. Check the methods too: a GET that is protected may have an unprotected DELETE.
API6: Unrestricted access to sensitive business flows
- Identify flows that can be abused at scale: sign-up, coupon redemption, checkout, invitations, referral credit.
- Add friction proportional to abuse: per-account limits, velocity checks, email or phone verification, bot detection where justified.
- Make payment and credit operations idempotent so replays and double clicks cannot grant twice.
API7: Server-side request forgery
If your API fetches a URL that a user supplies, such as a webhook target, an image URL or a link preview, an attacker can point it at your internal network or cloud metadata service.
- Require https and resolve the hostname yourself, then reject private, loopback and link-local address ranges, including 169.254.169.254.
- Re-check the address after every redirect, or do not follow redirects.
- Prefer allowlisting destinations over blocklisting bad ones.
- Do not return raw upstream responses or error bodies to the caller.
API8: Security misconfiguration
- Set security headers, disable verbose errors and debug modes in production, and remove default credentials.
- Restrict CORS to the exact origins you serve. Never combine a wildcard origin with credentials.
- Allow only the HTTP methods each route needs.
- Keep configuration identical in shape across environments, and review it as code.
API9: Improper inventory management
- Keep a current list of every route, version and environment. Old API versions and forgotten staging hosts are common entry points.
- Retire deprecated versions on a date, not "eventually".
- Generate documentation from the code or a schema so it cannot silently drift from what is deployed.
API10: Unsafe consumption of APIs
- Treat data from third-party APIs as untrusted input: validate its shape and size before using it.
- Verify webhook signatures using the provider's secret and a constant-time comparison, and reject stale timestamps.
- Use timeouts and limits on responses you consume, and do not follow redirects blindly.
Putting it into practice
Start with API1, API2 and API5. Authorization and authentication flaws are the ones that expose user data directly. Then add rate limits and size caps, and finish with the inventory and configuration items that keep the surface from growing unnoticed. Re-run the checks whenever a route is added.