Skip to main content

API Security Checklist Before Launch 2026

·APIScout Team
Share:

API Security Checklist: 20 Things to Check Before Launch

API security failures expose customer data, enable account takeovers, and create financial liability. This checklist covers the essential security measures every API should have before going live, organized by the OWASP API Security Top 10.

TL;DR

  • Broken Object Level Authorization (BOLA) is the #1 API vulnerability — check resource ownership on every request, not just at login
  • Short-lived JWTs (15-60 min) plus refresh token rotation eliminate most token theft risk; avoid alg: none and algorithm confusion attacks
  • SSRF can expose your entire internal network — validate and allowlist outbound URLs, block private IP ranges
  • Run OWASP ZAP against your staging environment before every major release
  • Security headers (HSTS, CORS allowlist, Content-Security-Policy) take 30 minutes to configure and prevent entire vulnerability classes

Authentication & Authorization

1. ✅ Use HTTPS Everywhere

No exceptions. HTTP exposes credentials and data in transit. Redirect HTTP to HTTPS. Use HSTS headers. Minimum TLS 1.2 (prefer 1.3).

2. ✅ Authenticate Every Request

Every endpoint (except public health checks) must require authentication. No unauthenticated access to data endpoints.

3. ✅ Use Strong Authentication

  • API keys for server-to-server
  • OAuth 2.0 + PKCE for user-delegated access
  • JWT with short expiration (15-60 minutes) + refresh tokens
  • Never accept credentials in URL query parameters

4. ✅ Implement Authorization Checks

Authentication ≠ authorization. Verify the authenticated user has permission to access the specific resource. Check on every request, not just at the gateway.

User 123 requests GET /users/456/orders
→ Is user 123 allowed to see user 456's orders?

5. ✅ Prevent BOLA (Broken Object Level Authorization)

The #1 API vulnerability. Always verify the requesting user owns or has access to the requested object. Don't rely on obscure IDs for security.

Input Validation

6. ✅ Validate All Input

Validate type, length, format, and range for every field. Reject unexpected fields. Use allowlists, not blocklists.

7. ✅ Sanitize for Injection

SQL injection, NoSQL injection, command injection, LDAP injection. Use parameterized queries. Never concatenate user input into queries.

8. ✅ Limit Request Body Size

Set maximum body size (e.g., 1MB). Prevent memory exhaustion from oversized payloads.

9. ✅ Validate Content-Type

Only accept expected content types (application/json). Reject unexpected types.

10. ✅ Limit Array and Object Depth

Prevent deeply nested JSON from consuming parsing resources. Limit array lengths and object nesting depth.

Rate Limiting & Throttling

11. ✅ Rate Limit All Endpoints

Every endpoint needs rate limits. Expensive operations (search, AI, file processing) need lower limits than simple reads.

12. ✅ Rate Limit by Authenticated Identity

Rate limit by API key or user, not just by IP address. IP-based limits penalize legitimate users behind NAT and are trivially bypassed with proxies.

13. ✅ Return Proper 429 Responses

Include Retry-After header. Include RateLimit-Remaining header. Machine-readable error response.

Data Protection

14. ✅ Don't Expose Sensitive Data

Never return passwords, tokens, internal IDs, or sensitive PII in API responses unless specifically requested. Use field-level access control.

15. ✅ Mask Sensitive Data in Logs

Don't log API keys, passwords, tokens, credit card numbers, or PII. Mask or redact before logging.

16. ✅ Implement Response Filtering

Only return the fields the client needs. Don't expose internal database fields, metadata, or debug information in production responses.

Infrastructure

17. ✅ Set Security Headers

Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Cache-Control: no-store (for sensitive data)

18. ✅ Implement CORS Properly

Don't use Access-Control-Allow-Origin: * for authenticated APIs. Allowlist specific origins.

19. ✅ Monitor and Alert

Log all authentication failures, authorization failures, and rate limit hits. Alert on anomalies — sudden spikes in 401s, unusual access patterns.

20. ✅ Prepare for Incidents

Have a plan for: API key compromise, data breach, DDoS attack. Know how to revoke keys, block IPs, and communicate with affected users.

Quick Reference: OWASP API Security Top 10

#RiskYour Mitigation
1Broken Object Level AuthorizationCheck ownership on every request
2Broken AuthenticationStrong auth, short-lived tokens
3Broken Object Property Level AuthorizationField-level access control
4Unrestricted Resource ConsumptionRate limiting, body size limits
5Broken Function Level AuthorizationRole-based access, admin endpoint protection
6Unrestricted Access to Sensitive Business FlowsAbuse detection, business logic protection
7Server-Side Request Forgery (SSRF)URL validation, block internal IPs
8Security MisconfigurationSecurity headers, error handling, defaults
9Improper Inventory ManagementTrack all API versions and endpoints
10Unsafe Consumption of APIsValidate data from third-party APIs

BOLA in Depth

Broken Object Level Authorization consistently ranks as the #1 API vulnerability because it is subtle, pervasive, and invisible without deliberate testing. The attack pattern is simple: a user who is legitimately authenticated changes an ID in the URL or request body to access another user's resource.

A real-world example: a ride-sharing API has GET /api/rides/{rideId} that returns ride details. If the API only checks that the requester has a valid session token — but does not verify that the ride belongs to the requesting user — any authenticated user can enumerate ride IDs and read every other user's ride history, pickup locations, and credit card partial data. This is BOLA. The attacker has a valid token; the authorization check is simply missing.

BOLA exploits follow the Insecure Direct Object Reference (IDOR) pattern, where internal object identifiers are exposed in the API and can be manipulated. Using UUIDs instead of sequential integers reduces discoverability but does not prevent BOLA — security through obscurity is not a fix. The fix is always an authorization check.

The implementation must happen at the data layer, not just at the route or middleware layer. A middleware that checks "is this user logged in?" catches authentication failures but not authorization failures. The check must be: "is the logged-in user allowed to access this specific resource?"

// Wrong — only checks authentication
app.get('/api/orders/:orderId', requireAuth, async (req, res) => {
  const order = await db.orders.findUnique({ where: { id: req.params.orderId } });
  res.json(order);
});

// Correct — checks ownership too
app.get('/api/orders/:orderId', requireAuth, async (req, res) => {
  const order = await db.orders.findUnique({
    where: {
      id: req.params.orderId,
      userId: req.user.id, // ownership check in the query itself
    },
  });

  if (!order) {
    return res.status(404).json({ error: { code: 'not_found', message: 'Order not found' } });
  }

  res.json(order);
});

Note that the ownership check is in the database query — the order is fetched only if it belongs to the requesting user. This is preferable to fetching the order and then checking ownership in application code, because the two-step approach has a race condition risk and is more verbose. If the order doesn't exist or doesn't belong to the user, return 404 (not 403), so attackers cannot distinguish between "doesn't exist" and "exists but you don't own it."

For multi-tenant APIs, BOLA extends to the tenant level. A user in tenant A should never be able to access resources belonging to tenant B. Always scope every database query with both the user ID and the tenant ID. For a comprehensive view of multi-tenant API security patterns, see our guide on building multi-tenant APIs.

JWT Security in Practice

JWTs are widely used for API authentication, but several common implementation mistakes turn JWTs from a security mechanism into a vulnerability.

Short expiry plus refresh token rotation is the correct pattern. Access tokens should expire in 15-60 minutes. Long-lived access tokens (days or weeks) mean a stolen token is valid for days or weeks. Refresh tokens enable transparent re-authentication without requiring the user to log in again. When a refresh token is used, immediately invalidate it and issue a new one — this is rotation. If a stolen refresh token is used, the legitimate user's next refresh will fail, signaling compromise.

Algorithm confusion attacks exploit JWT implementations that accept multiple signing algorithms. The alg field in the JWT header specifies how the token is signed. If your backend accepts both RS256 (asymmetric) and HS256 (symmetric), an attacker can take a valid RS256 token, change the header to alg: HS256, and re-sign it with your public key — which, for HS256, is treated as the symmetric secret. The fix is to explicitly require a single algorithm and reject tokens with any other alg value:

import jwt from 'jsonwebtoken';

function verifyToken(token: string): JWTPayload {
  return jwt.verify(token, process.env.JWT_SECRET!, {
    algorithms: ['HS256'], // Never accept 'none' or multiple algorithms
  }) as JWTPayload;
}

Never allow alg: none. Some early JWT libraries accepted unsigned tokens with alg: none, treating them as valid. Always specify the algorithm explicitly.

Storage tradeoffs: HttpOnly cookies are more secure than localStorage because JavaScript cannot read them — XSS attacks cannot steal the token. The tradeoff is CSRF vulnerability, which is addressed with the SameSite=Strict cookie attribute and CSRF tokens for state-changing operations. localStorage is simpler to implement but exposes tokens to any XSS attack on your page. For browser-based applications, HttpOnly + SameSite=Strict is the recommended pattern.

JWT revocation is the hardest problem with JWTs: because they're stateless, you cannot invalidate them before expiry without a server-side state check. Solutions include a blocklist (Redis set of revoked token JTIs checked on every request) or short expiry windows (15 minutes). For logout, add the token's JTI to the blocklist and expire the entry at the token's original expiry time.

For a comprehensive treatment of JWT patterns, see our guide on API authentication patterns with JWT.

SSRF Prevention

Server-Side Request Forgery (SSRF) is OWASP API Security #7 and ranks among the most dangerous vulnerabilities because it converts your API server into an unwilling proxy for attacking internal systems. When your API accepts a user-supplied URL and fetches it server-side (e.g., "upload an image from URL," "fetch metadata for this link"), an attacker can supply an internal URL like http://169.254.169.254/latest/meta-data/ (AWS instance metadata endpoint) and read credentials, private keys, or internal service responses.

The impact of SSRF can be catastrophic. Attackers have used SSRF to:

  • Read AWS/GCP/Azure instance metadata and extract IAM credentials
  • Access internal services on private networks (Redis, Elasticsearch, internal APIs) that are not exposed to the internet
  • Port-scan internal networks using timed responses
  • Read internal documentation, configuration files, and secrets

The correct defense is URL validation with an allowlist, not a blocklist. Blocklists are bypassed with alternative representations (http://0x7f000001 for 127.0.0.1, IPv6 ::1, URL encoding, domain redirection). Allowlists specify exactly which domains and protocols are permitted:

import { URL } from 'url';
import dns from 'dns/promises';

const PRIVATE_IP_RANGES = [
  /^127\./,          // Loopback
  /^10\./,           // Private class A
  /^172\.(1[6-9]|2[0-9]|3[01])\./,  // Private class B
  /^192\.168\./,     // Private class C
  /^169\.254\./,     // Link-local (AWS metadata)
  /^::1$/,           // IPv6 loopback
  /^fc00:/,          // IPv6 private
];

async function validateFetchUrl(userUrl: string): Promise<string> {
  let parsed: URL;
  try {
    parsed = new URL(userUrl);
  } catch {
    throw new Error('Invalid URL format');
  }

  // Only allow HTTPS
  if (parsed.protocol !== 'https:') {
    throw new Error('Only HTTPS URLs are allowed');
  }

  // Allowlist specific domains if possible
  const allowedDomains = ['images.unsplash.com', 'cdn.example.com'];
  if (!allowedDomains.some(d => parsed.hostname.endsWith(d))) {
    throw new Error('Domain not in allowlist');
  }

  // Resolve DNS and check for private IPs
  const addresses = await dns.resolve4(parsed.hostname);
  for (const ip of addresses) {
    if (PRIVATE_IP_RANGES.some(range => range.test(ip))) {
      throw new Error('URL resolves to a private IP address');
    }
  }

  return parsed.toString();
}

The DNS resolution check is important because an attacker can register a domain that resolves to an internal IP. Check after DNS resolution, not just the hostname string.

Security Testing Your API

Security testing should be part of your development workflow, not a one-time pre-launch audit. Automated scanning catches low-hanging fruit; manual testing catches business logic vulnerabilities.

OWASP ZAP (Zed Attack Proxy) is the standard open-source tool for automated API security scanning. For REST APIs, you can import your OpenAPI specification into ZAP and run an Active Scan that automatically tests every endpoint for common vulnerabilities: SQL injection, XSS, path traversal, authentication bypass, and more. Run ZAP against your staging environment as part of your CI/CD pipeline:

docker run -t ghcr.io/zaproxy/zaproxy:stable zap-api-scan.py \
  -t https://staging.api.example.com/openapi.json \
  -f openapi \
  -r zap-report.html

ZAP scans catch the obvious issues. For deeper testing — especially BOLA, authorization bypass, and business logic flaws — manual testing is required. Burp Suite Community Edition (free) lets you intercept API requests, modify parameters, and replay requests with different values. Test for BOLA by: creating two test accounts (A and B), creating a resource with account A, then accessing that resource using account B's token. If account B can see account A's data, you have BOLA.

Testing checklist for each endpoint:

  • Authentication bypass: request without token, with expired token, with malformed token
  • BOLA: change object IDs to IDs belonging to other users
  • Rate limiting: automated requests to verify 429 triggers at stated limits
  • Input validation: oversized payloads, deeply nested JSON, unusual character encodings
  • Error information disclosure: do error messages reveal internal paths, database names, or stack traces?

Security Headers in Detail

Security headers are the lowest-effort, highest-value security improvement available. They require no code changes to your API logic — just configuration in your web server or API gateway.

HSTS preloading (Strict-Transport-Security: max-age=31536000; includeSubDomains; preload) tells browsers to always use HTTPS for your domain, even on the first request. Preloading submits your domain to the browser's built-in HSTS list, so the protection works even before the first header is seen. Submit your domain at hstspreload.org.

Content-Security-Policy for APIs (Content-Security-Policy: default-src 'none') is appropriate for API-only services. It prevents the browser from executing any inline scripts or loading external resources if your API response is somehow rendered as HTML (a common vector for stored XSS if your API powers any web interface).

X-Content-Type-Options: nosniff prevents browsers from MIME-type sniffing — interpreting a response differently from the declared Content-Type. Without it, a browser might interpret a JSON response containing a script as executable JavaScript.

CORS deep dive: Preflight requests (OPTIONS) are triggered when: the method is not GET/POST/HEAD, or when custom headers like Authorization are sent. Your CORS configuration must respond correctly to preflight requests. The Access-Control-Allow-Credentials: true header allows cookies to be sent cross-origin — but if you set this, Access-Control-Allow-Origin must be an explicit origin, never *. Wildcards with credentials are rejected by browsers as a security violation.

// Express CORS configuration for authenticated API
import cors from 'cors';

const corsOptions = {
  origin: (origin: string | undefined, callback: Function) => {
    const allowedOrigins = [
      'https://app.example.com',
      'https://staging.example.com',
    ];
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'Idempotency-Key'],
};

app.use(cors(corsOptions));

For a broader view of API security concerns across design and deployment, see our guide on how to design a REST API developers love and the API testing strategies guide.

Conclusion

Security is not a checklist you complete once — it is an ongoing practice. The 20 items in this checklist represent the minimum bar for a public API. BOLA prevention, JWT security, SSRF blocking, and automated security testing address the most common attack vectors. Security headers and CORS configuration take minutes to configure but eliminate entire vulnerability classes.

Treat every item on this list as a non-negotiable before launch, and build security testing into your development workflow so new vulnerabilities don't slip in as the API evolves. The cost of a security incident — financially, reputationally, and in customer trust — is orders of magnitude higher than the cost of prevention.

The API Integration Checklist (Free PDF)

Step-by-step checklist: auth setup, rate limit handling, error codes, SDK evaluation, and pricing comparison for 50+ APIs. Used by 200+ developers.

Join 200+ developers. Unsubscribe in one click.