Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

API Security Checklist

The controls that keep an API from becoming a breach — auth, authorization, input handling, transport, and the OWASP API Top-10 traps.

API security comes down to answering two questions on every request — who is calling (authentication) and may they do this, to this object (authorization) — and then treating everything in the request as hostile input. Most real breaches are not broken cryptography; they are a missing ownership check on an endpoint someone forgot existed.

The sections run from identity to inputs to operations, and the OWASP API Security Top 10 (2023) maps each risk to its fix. Use the sheet as a review checklist before launch, and again whenever you add an endpoint.

Authentication

Every non-public route
Require a verified identity — no "internal" endpoint left open.
Tokens
Short-lived access tokens + refresh; verify signature, issuer, audience, expiry.
Secrets
Never hardcode. Use a vault/KMS/env; rotate; never log tokens or keys.
Cookies
HttpOnly; Secure; SameSite. Protect state-changing routes from CSRF.
JWT validation
pin the expected algorithm, reject alg: none, check iss, aud, exp, nbf
Algorithm confusion
an RS256 public key accepted as an HS256 secret lets anyone forge tokens
OAuth 2.0 + PKCE
authorization code with PKCE for browser and mobile apps — never the implicit flow
API keys
store only a hash, show the key once, add a scannable prefix, support rotation
Passwords
hash with Argon2id, scrypt or bcrypt — never a fast hash such as SHA-256
Credential stuffing
rate-limit logins per account and IP, offer MFA, reject known-breached passwords
Service to service
mTLS or short-lived workload identity tokens instead of shared static secrets

Authorization (the #1 API risk)

Object-level (BOLA)
Check the caller owns the object — GET /orders/123 must verify 123 is theirs. #1 OWASP API risk.
Function-level
Enforce roles per action — don’t rely on the UI hiding admin buttons.
Deny by default
Allowlist what’s allowed; new endpoints start locked.
Mass assignment
Bind only allowed fields — never let a request set role or isAdmin.
Property level (BOPLA)
return only fields the caller may see, accept only fields they may change
Server-side, every request
enforce it in middleware or the data layer — never only in the client or gateway
Unguessable IDs
UUIDs slow enumeration but never replace an ownership check
Tenant isolation
scope every query by tenant id in one place, such as row-level security

Input & output

Validate everything
Schema-validate body, query, params. Reject unknown fields; bound sizes.
Parameterized queries
Always — the fix for SQL/NoSQL injection. Never string-concat inputs.
Encode output
Context-aware encoding to stop XSS; set Content-Type explicitly.
Rate limit & quota
Per-key/IP limits + payload size caps — blunt scraping, brute force, and DoS.
SSRF
allowlist outbound hosts; block private ranges and 169.254.169.254; require IMDSv2
Pagination caps
enforce a maximum page size on the server — ?limit=1000000 is a DoS
GraphQL limits
cap query depth, complexity and batching; disable introspection in production
File uploads
check size and type by content, rename, store outside the web root, scan before serving
Sensitive responses
Cache-Control: no-store, so tokens and PII never land in shared caches

Transport & hygiene

TLS only
HTTPS everywhere + HSTS. Redirect HTTP → HTTPS.
Minimal errors
Generic error messages to clients; details to logs. No stack traces in responses.
CORS narrowly
Explicit allowed origins; never * with credentials.
Security headers
CSP, X-Content-Type-Options: nosniff, X-Frame-Options.
Log & monitor
Audit auth failures and anomalies; alert on spikes. Don’t log secrets/PII.
TLS versions
TLS 1.2 at minimum, 1.3 preferred; switch off legacy protocols and weak ciphers

OWASP API Top 10 (2023)

API1 BOLA
Broken Object Level Authorization — no ownership check on an object id
API2
Broken Authentication — weak login, token or session handling
API3 BOPLA
Broken Object Property Level Authorization — excessive data exposure plus mass assignment
API4
Unrestricted Resource Consumption — no limits on size, rate, cost or third-party spend
API5
Broken Function Level Authorization — admin functions reachable by regular users
API6
Unrestricted Access to Sensitive Business Flows — bots buying out stock, mass sign-ups
API7
Server Side Request Forgery — the API fetches a URL the attacker chose
API8
Security Misconfiguration — verbose errors, open CORS, missing headers, default settings
API9
Improper Inventory Management — forgotten old versions and undocumented endpoints
API10
Unsafe Consumption of APIs — blindly trusting what third-party APIs return

Webhooks & integrations

Sign webhooks
HMAC-SHA256 over the timestamp plus the raw body, with a per-endpoint secret
Verify on receipt
recompute the HMAC over the raw bytes and compare in constant time
Replay window
reject timestamps older than about five minutes and deduplicate event ids
Outbound calls
timeouts, response size limits and schema validation on every third-party API

Testing & operations

Two-account test
call every endpoint with user B’s token against user A’s objects — the BOLA test
API inventory
an up-to-date OpenAPI spec per version, and a schedule for retiring old ones
Secret scanning
scan commits and CI logs for keys; rotate immediately after any leak
Dependency scanning
track CVEs in libraries and base images and patch on a fixed timeline
DAST and pentests
automated scans in CI, plus a manual test before major launches

Recipes

Verify a webhook signature with a replay window

import hashlib, hmac, time

def verify_webhook(raw_body: bytes, timestamp: str, signature: str, secret: bytes) -> bool:
    if abs(time.time() - int(timestamp)) > 300:        # older than 5 minutes: possible replay
        return False
    signed = timestamp.encode() + b"." + raw_body       # sign exactly what was sent
    expected = hmac.new(secret, signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)     # constant-time comparison

Verify the raw bytes before parsing: re-serializing the JSON changes spacing and breaks the signature. Also store processed event ids, so a replay inside the window is rejected too.

Test for BOLA with two accounts

1. create users A and B; as A, create an order  ->  id 1042
2. as B:  GET    /orders/1042          ->  expect 404 (or 403), never 200
3. as B:  PATCH  /orders/1042 {...}    ->  expect 404 / 403, and nothing changed
4. as B:  DELETE /orders/1042          ->  expect 404 / 403
5. repeat for every id the API accepts: in the path, query string, body and headers
6. automate it: one negative test per endpoint, run in CI

Scanners rarely find BOLA because the responses look normal; only a test that knows who owns what can. Include ids nested inside request bodies, which are the most often missed.

Validate a JWT, in order

1. parse the header; reject alg "none" and any algorithm you did not configure
2. pick the key by kid from your own JWKS, never from a URL inside the token
3. verify the signature with that key and the expected algorithm
4. check iss (who issued it) and aud (that it is meant for this API)
5. check exp and nbf, allowing about 60 seconds of clock skew
6. only then trust the claims: subject, scopes, tenant

Use a maintained library and configure the allowed algorithms explicitly. Skipping the aud check lets a token issued for one of your services be replayed against another.

Rate limits that blunt abuse

per API key:    1,000 requests per minute, token bucket (short bursts allowed)
per IP:         login 5 attempts per minute, then an exponential lockout
per account:    password reset 3 per hour
over the limit: 429 Too Many Requests + Retry-After: 30
expensive ops:  separate, lower quotas for exports, search and LLM calls, plus payload caps

Limit on the identity that matters: per key for API abuse, per account for credential attacks, since attackers rotate IPs. Return the same response for unknown and wrong passwords so limits cannot be used to enumerate users.

Check security headers and CORS from the shell

curl -sI https://api.example.com \
  | grep -iE 'strict-transport|content-security|x-content-type|x-frame|cache-control'
curl -s -o /dev/null -w '%{http_code}\n' http://api.example.com        # expect 301/308 to https
curl -sI -H 'Origin: https://evil.example' https://api.example.com \
  | grep -i access-control-allow-origin                                # should print nothing

An API that echoes any Origin back in Access-Control-Allow-Origin, especially with credentials allowed, lets other websites read responses as your logged-in users.

Questions about API Security Checklist

What is BOLA (Broken Object Level Authorization)?

BOLA is the number one risk in the OWASP API Security Top 10. It happens when an API takes an object identifier from the request, such as an order id in the URL, and returns or changes that object without checking that the caller is allowed to access it. An attacker simply changes the id, from 1042 to 1043, and reads someone else’s data. The fix is an ownership or permission check on every request that touches an object, enforced on the server, typically by scoping database queries to the authenticated user or tenant. Unguessable ids help but are not a fix.

What is the OWASP API Security Top 10?

It is a list, maintained by the OWASP Foundation, of the most critical security risks specific to APIs; the current edition was published in 2023. It covers broken object level authorization, broken authentication, broken object property level authorization, unrestricted resource consumption, broken function level authorization, unrestricted access to sensitive business flows, server side request forgery, security misconfiguration, improper inventory management, and unsafe consumption of third-party APIs. Most of the list is about authorization and limits rather than cryptography, which matches where real API breaches happen. Teams use it as a design and code review checklist.

Should I use API keys or OAuth?

Use API keys for server-to-server access where the caller is a system you trust to keep a secret, such as a customer’s backend calling your API: they are simple, but long-lived and tied to an account rather than a user. Use OAuth 2.0 when an application acts on behalf of a user, when you need scoped, revocable and short-lived access tokens, or when third-party apps connect to your platform. Browser and mobile apps should use the authorization code flow with PKCE. Many platforms offer both, and either way keys and tokens belong in headers, never in URLs.

What is the difference between authentication and authorization?

Authentication establishes who the caller is, for example by validating a password, an API key, a session cookie or a signed token. Authorization decides what that authenticated caller may do: which endpoints they can call, which objects they can read or change, and which fields they can see or edit. A request can pass authentication and still be forbidden. A failed authentication should return 401, a failed authorization 403, or 404 when you do not want to reveal that a resource exists. Most serious API vulnerabilities are authorization gaps, so check it on every request, for every object.