The API security checklist

Sat Aug 22 2026

The API security checklist

A valid JWT proves who the caller is. It doesn’t prove they can read order 12345. The failure I’d prioritize is usually after authentication: ownership, field permissions, resource limits, or business rules were never enforced.

But this API security best practices checklist is for production reviews in 2026. It follows one request from identity through permission, resource use, input handling, transport, detection, secrets, and verification. Copy the checks into engineering tickets or pass/fail tests.

And this checklist is strongest on authorization and request handling. It doesn’t replace threat modeling for a regulated payment system or a review of your identity provider’s implementation.

In this article

There is no OWASP API Security Top 10 2026 edition

And the OWASP API Security Project lists API Security Top 10:2023, released June 5, 2023, as the current stable edition. Calling this “the OWASP 2026 list” would be inaccurate; 2026 is the review date, not the edition number.

So the taxonomy covers API1 Broken Object Level Authorization, API3 Broken Object Property Level Authorization, API4 Unrestricted Resource Consumption, API5 Broken Function Level Authorization, API6 Sensitive Business Flows, API7 SSRF, API9 Improper Inventory Management, and API10 Unsafe Consumption of APIs. OWASP also covers broken authentication and security misconfiguration.

OWASP provides the risk taxonomy. My practical use of it is simpler: map each route to an owner, an authorization predicate, and a test. Blackhawk’s REST API penetration testing guide covers the hands-on testing that follows the design review.

[IMAGE PLACEHOLDER: OWASP API Security Top 10 (2023) visual, clearly labelled “current stable edition as of 22 August 2026”; source/link to OWASP API Security Top 10]

Authentication depends on the client and trust boundary

Start by separating what OAuth, JWT, and API keys each do. OAuth 2.0 is an authorization framework, JWT is a token format, and an API key is a credential. An API key usually identifies an application or integration; it carries no object-level permission by itself.

Client or integrationSuitable starting pointTestable checks
Browser or mobile userAuthorization Code with PKCEValidate the redirect URI, authorization-code exchange, PKCE verifier, token audience, and scopes
Machine client in one trust boundaryOAuth 2.0 Client CredentialsGive each client narrow scopes; verify client authentication and revoke credentials during offboarding
Cross-organization or regulated integrationmTLS or OAuth with mTLSValidate the client certificate, certificate chain, expiry, and certificate or token binding
Low-sensitivity scheduled job or webhookAPI keyScope the key, rotate it, revoke it, and apply endpoint and tenant limits
Public client needing sender-constrained tokensDPoP where your provider and libraries support itVerify the proof signature, HTTP method, request URL, nonce or replay behavior, and token binding

And use a maintained OAuth library or identity service. Skycloak’s comparison is useful for choosing between client types, but your API still owns the final security decision.

OAuth gets a client an access token; your API still decides whether that token may perform this operation on this object. Your server must make those decisions.

For every bearer or JWT access token, check its signature against a trusted key set. Verify the allowed algorithm, issuer, audience, expiry, and required claims. Never put client secrets in browser bundles or mobile apps. The Node.js security guide and Django security guide cover framework-specific token and middleware handling.

JWT validation still needs a revocation strategy

For GET /profile or an ordinary read operation, local validation is often practical. But account recovery and payment authorization may need an introspection call to check current token state.

MethodStrengthCost
Local validation with JWKSFast, offline-capable, and independent of a request-time authorization-server callA revoked token can remain usable until expiry
IntrospectionReflects current token state and catches revocation promptlyAdds latency and an authorization-server dependency

Use local validation when the expiry window fits your risk model. Consider introspection for payment and account recovery. Use it too when permission changes or another path makes revocation critical. Shortening the lifetime reduces exposure; it never replaces object authorization or a revocation control.

A 15-minute access token and seven-day refresh token are concrete starting points from secondary practitioner guidance; OWASP does not require them. I can’t choose your expiry window without knowing your client mix, revocation needs, and incident tolerance.

Pin accepted algorithms, rotate signing keys, and reject alg:none, unapproved algorithms, the wrong issuer, the wrong audience, and expired tokens. Test those failures directly.

A valid token does not authorize an object

Take a multi-tenant order API. This handler authenticates the caller, then trusts the caller-supplied identifier:

app.get("/api/orders/:id", auth, async (req, res) => {
  const order = await db.orders.findById(req.params.id);
  return res.json(order);
});

A safer lookup carries the authorization predicate into the data access operation:

app.get("/api/orders/:id", auth, async (req, res) => {
  const order = await db.orders.findOne({
    id: req.params.id,
    tenantId: req.user.tenantId,
    ownerId: req.user.id
  });

  if (!order) {
    return res.sendStatus(404);
  }

  return res.json(order);
});

This example assumes only the owner may view an order. If an order is visible to several users in a tenant, replace ownerId with the actual policy predicate: tenant membership, account relationship, role, or another explicit rule.

OWASP API1 puts the requirement plainly: “Object level authorization checks should be considered in every function that accesses a data source using an ID from the user.” Apply that check to reads, updates, deletes, nested resources, exports, and background actions.

The 404 response is a common choice for objects outside the caller’s visible scope because it avoids revealing whether the object exists. I’d model staff access as a separate, explicit policy rather than silently removing the ownership condition for everyone.

BOLA, or Broken Object Level Authorization, is the OWASP term. Teams still commonly call identifier-swapping flaws IDOR. For BOLA and IDOR prevention, test with two ordinary users, not an administrator:

  • Swap identifiers. Change numeric IDs, UUIDs, nested identifiers, and tenant identifiers.
  • Cover every method. Test GET, PATCH, DELETE, and export paths.
  • Test the boundary. Use users from the same tenant and different tenants.
  • Check inference. Compare status codes, response sizes, and timing for inaccessible objects.

A low-privilege user must fail every attempt to read, modify, delete, export, or infer another tenant’s order. Use the Node.js guide for Express implementation details; run the full procedure through your security test process.

Field-level permissions block mass assignment

Spreading req.body lets a caller attempt changes to status, ownerId, or paymentState:

await db.orders.update(req.params.id, {
  ...req.body
});

A writable role or isVerified field creates privilege escalation. Returning internal fields creates excessive data exposure. OWASP API3:2023 groups both older concerns under Broken Object Property Level Authorization.

Validate the body first, then construct an allow-list:

const body = updateOrderSchema.parse(req.body);

const updates = {
  shippingAddress: body.shippingAddress,
  deliveryInstructions: body.deliveryInstructions
};

await db.orders.updateOwned(req.params.id, req.user.id, updates);

The schema step is shown here as a permission illustration; it still needs field types, lengths, and nested-value rules.

Keep server-controlled workflow changes separate from ordinary updates. A customer may edit a shipping address, while only an authorized workflow can change payment state. Return response DTOs rather than database objects, so audit fields, tenant metadata, payment details, and administrative flags stay internal.

Object and property checks leave a third boundary: whether this caller may invoke the function at all. API5 covers that function-level decision, especially for administrative routes. Test attempts to call admin endpoints with ordinary user tokens.

Make abusive requests expensive to send and cheap to reject

Rate limiting is not a decorative gateway setting. Login, password reset, search, export, and pagination need different limits. Expensive business actions need them too. Tenant quotas matter as much as IP limits.

This is a ticket-ready starting policy:

  • Login: five attempts per 15 minutes per IP, with an identity-based control where appropriate.
  • Password reset: stricter limits than ordinary reads, with abuse alerts.
  • Search and export: separate endpoint limits, bounded result sets, and per-tenant quotas.
  • Pagination: enforce a maximum limit; reject unbounded list requests.
  • Business flows: limit checkout-like actions by identity, tenant, and business state; IP limits alone are insufficient.
  • Failure behavior: return 429 Too Many Requests and Retry-After; apply a conservative fallback if limiter state is unavailable.

The five-attempts-per-15-minutes-per-IP example comes from Quantlab’s practitioner guidance. It is not an OWASP default. Shared office networks and mobile carriers can place many users behind one address, so measure false positives before adopting it.

For the order API, test oversized GET /api/orders?limit=... values, repeated exports, expensive searches, and repeated checkout-like requests while ordinary reads remain below their limit. Limits should bound bandwidth, CPU, memory, storage, and business capacity.

Example response only; use the format your gateway and clients support:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
RateLimit-Limit: 60
RateLimit-Remaining: 0

Validate at the boundary and reject unsafe input

Validate path, query, header, and body values before business logic or persistence handles them. Strict schemas such as Zod, JSON Schema, and Pydantic should enforce types and required fields. They should also enforce lengths, ranges, formats, enumerated values, and permitted nesting.

Use this boundary checklist:

  • Schema: reject values outside the contract; normalize only where the contract defines normalization.
  • Business rules: require positive, bounded quantities; a shipped order must not return to pending.
  • Content: verify Content-Type, payload size, and accepted encodings.
  • Database: use prepared statements and parameterized queries. Blackhawk’s SQL injection guide covers query construction and injection-specific testing.
  • URLs: if the API fetches a remote resource, allow-list destinations. Block internal and cloud-metadata ranges. Validate every destination the HTTP client will actually request.
  • Third-party data: parse provider responses against a schema, enforce size limits, and keep their fields away from privileged update paths.

OWASP API7 covers SSRF, while API10 covers unsafe consumption of external APIs. A string that looks like a URL is still user input.

Internal calls need TLS too

Enforce HTTPS and TLS for public endpoints and internal service calls. An internal route can still be reached after a routing error, compromised workload, or misconfigured proxy. Require authenticated service identity whenever a downstream service makes authorization decisions.

Check three things:

  1. Plaintext HTTP is rejected or redirected according to your deployment policy.
  2. Outbound clients verify certificates and reject invalid, expired, or unexpected identities.
  3. Webhook tests reject missing or invalid signatures, stale timestamps, and reused event IDs when the provider supplies replay controls.

Use your platform’s current supported secure TLS configuration instead of copying a static cipher list into a checklist. Keep bearer tokens out of URLs, analytics data, and logs.

Logs should explain the denial

Most API reviews overinvest in token validation because it is easy to demonstrate. Logging gets the opposite treatment: a vague “add observability” ticket and no evidence that an investigator could reconstruct an attack.

For an authorization denial, record the least sensitive identifier that still supports investigation, along with retention and access controls. A schema sketch should contain:

{
  "event": "authorization_denied",
  "requestId": "<request identifier>",
  "actor": "<user or service identifier>",
  "tenant": "<tenant identifier>",
  "requestPath": "/api/orders/:id",
  "endpoint": "GET /api/orders/:id",
  "object": "<order reference>",
  "action": "read",
  "policy": "order_owner_or_tenant_member",
  "reason": "object_outside_actor_scope",
  "outcome": "denied",
  "timestamp": "<UTC timestamp>",
  "clientContext": "<redacted network or client context>"
}

Log authentication failures and authorization denials. Log high-value actions, exports, rate-limit events, and access to deprecated or debug endpoints. Never log passwords, access tokens, or sensitive payloads.

Alert on repeated 401 and 403 responses, identifier-probing patterns, unusual exports, and sudden rate-limit spikes. Maintain an endpoint and version inventory. After an authentication or data-access change, I’d insist on a fresh pentest run rather than trusting the old report.

Secrets are credentials, so rotate them when exposure is possible

Store API keys, database passwords, signing keys, and service credentials in a managed secrets store. Scope each secret to its minimum permissions and environment. Never embed secrets in browser bundles or mobile apps; clients can extract them.

When a credential may have leaked:

  1. Identify every system that uses it.
  2. Revoke or disable it immediately.
  3. Issue a replacement with narrower scope.
  4. Remove it from source, history, build artifacts, logs, and error responses.
  5. Review access records for misuse.
  6. Rotate related credentials if they shared the same exposure.

A leaked key is an incident, even when you have no evidence of use. Prefer short-lived tokens where the integration supports them, and rotate on schedule as well as on suspicion.

Run the checklist as tests, not as a policy document

Copy these rows into your release ticket. Each pass condition names evidence a reviewer can inspect.

CheckPass conditionEvidence
Endpoint inventoryEvery host, route, version, debug endpoint, and deprecated version is knownVersioned route inventory with owner and retirement date
AuthenticationThe mechanism fits the client; issuer, audience, expiry, signature, algorithm, and claims are validatedMiddleware tests showing accepted and rejected tokens
Object authorizationCross-user and cross-tenant identifier changes fail for reads, updates, deletes, nested resources, and exportsTest matrix using low-privilege users and response assertions
Property authorizationWritable fields are allow-listed; sensitive fields cannot be mass-assignedSchema, DTO, and negative field-mutation tests
Function authorizationAdministrative and ordinary functions enforce separate policiesRole or policy tests for every privileged route
Resource controlsPagination, payloads, queries, exports, business flows, and tenant usage are bounded429 tests, quota configuration, and maximum-value tests
ValidationSchemas, content types, URLs, SQL inputs, and third-party responses are validatedBoundary test results and prepared-query review
TransportTLS is enforced on every hop and webhook signatures are verifiedPlaintext, certificate, signature, timestamp, and replay tests
DetectionSecurity events include actor, tenant, route, action, outcome, reason, and time without secretsRedacted event samples and alert rules
SecretsCredentials are scoped, stored securely, rotated, and revocableSecrets inventory, rotation record, and repository scan
RetestingAuthentication and data-access changes trigger security testing before releaseLinked test run or approved exception

For the order API, create Alice and Bob in tenant A. Put Carol in tenant B. Test every combination of order IDs and tenant context. Attempt field mutation and oversized pagination. Repeat exports and try to abuse business flows. The OWASP API Security Project also lists crAPI, an intentionally vulnerable API project, for safe practice.

No cross-tenant denial test, no release.