APIs are the backbone of modern web applications — and the most actively targeted attack surface. Every major breach of the last five years involved APIs in some capacity: credential stuffing against authentication endpoints, IDOR on data retrieval endpoints, missing rate limits enabling brute-force attacks, or overly permissive CORS policies leaking data cross-origin. This checklist covers the 12 security controls that every API must have before launch, with concrete verification steps for each.
1. Authentication on Every Endpoint
Every endpoint that returns or modifies user data must require a valid, verified authentication token. Test this by removing or invalidating your authentication header and confirming the server returns 401 Unauthorized — not 200 OK, not 403 Forbidden, and certainly not the data. Common failure: new routes added under time pressure without the authentication middleware that protects other routes.
Verify: curl -X GET https://api.example.com/users/me (no auth header) → must return 401.
2. Authorization on Every Data Access
Authentication confirms who you are; authorisation confirms what you are allowed to access. Every endpoint that retrieves or modifies a specific resource must verify that the authenticated user owns or has permission to access that resource — not just that they are logged in. Test by authenticating as user A and attempting to access user B's resources by ID.
Verify: Log in as user A. Note one of user A's resource IDs. Log in as user B. Try to access user A's resource. Server must return 403 or 404.
3. Rate Limiting on All Endpoints
Without rate limiting, your authentication endpoint is a brute-force target, your data endpoints are enumeration targets, and your write endpoints can be spammed. Rate limiting must be applied globally and additionally tightened on high-value endpoints: login, password reset, OTP verification, registration, and any endpoint that sends an email or SMS. A missed rate limit on password reset is one of the most common account takeover vectors.
Verify: Send 50 rapid requests to your login endpoint. You must receive 429 Too Many Requests before the 50th attempt. Check that the Retry-After header is set.
4. Input Validation and Sanitisation
Every piece of data arriving from the client is untrusted. Validate type, length, format, and range before processing. Reject rather than sanitise where possible — a user ID field that receives a string should return 400, not attempt to coerce the string. Pay particular attention to fields that end up in database queries, file paths, shell commands, or rendered HTML.
Verify: Send {"user_id": "../../etc/passwd"} to an endpoint expecting a numeric user ID. Server must return 400. Verify no file system access occurred.
5. Parameterised Queries — No Dynamic SQL
If any endpoint constructs SQL queries by concatenating user input into query strings, it is vulnerable to SQL injection. Every database interaction must use parameterised queries or prepared statements. ORMs use parameterised queries by default, but raw query fallbacks are common in codebases and are easily missed in code review.
Verify: Audit every database call in your codebase. Grep for string concatenation patterns in SQL: grep -rn "query.*+" --include="*.js". All database interactions must use driver-provided parameterisation.
6. HTTPS Only — No HTTP Fallback
All API traffic must be encrypted in transit. There must be no HTTP endpoint that accepts real API traffic — a plain HTTP request should be immediately redirected to HTTPS with a 301, and the Strict-Transport-Security header must be set on all HTTPS responses to prevent protocol downgrade attacks in future requests.
Verify: curl -v http://api.example.com/health → must receive a 301 redirect to HTTPS, not a 200 response.
7. Correct CORS Policy
CORS (Cross-Origin Resource Sharing) headers tell browsers which origins can make authenticated cross-site requests to your API. A wildcard Access-Control-Allow-Origin: * on an authenticated API is a critical misconfiguration: it allows any website to make credentialed requests to your API on behalf of a logged-in user. Your CORS policy must enumerate only the exact origins that need access — your frontend domain — and must not include null as an allowed origin.
Verify: curl -H "Origin: https://evil.example.com" https://api.example.com/users/me → the response must not contain Access-Control-Allow-Origin: https://evil.example.com or Access-Control-Allow-Origin: *.
8. Security Headers on API Responses
API responses need a focused set of security headers. At minimum: X-Content-Type-Options: nosniff (prevents MIME-type sniffing), X-Frame-Options: DENY (prevents clickjacking if responses are rendered in iframes), and Content-Security-Policy if your API returns HTML. JSON APIs do not need CSP but should include the others. Remove X-Powered-By and Server headers to avoid version disclosure.
9. Sensitive Data Minimisation in Responses
APIs routinely return more data than the client needs. A user profile endpoint that returns the full database row — including hashed password, internal flags, and linked PII fields — leaks information even when the client only displays a name and avatar. Audit every API response and return only the fields the client actually uses. This also applies to error responses: never return stack traces, database error messages, or internal paths in production.
Verify: Inspect every response body in your API. Fields like password_hash, internal_notes, admin_flag, or stripe_customer_id should never appear in client-facing responses.
10. Token Expiry and Refresh Logic
Access tokens must expire. A JWT with no expiry — or a 10-year expiry — is equivalent to a permanent password stored in the browser. Access tokens should expire in minutes to hours; refresh tokens should expire in days to weeks and must be rotatable (invalidated after use, with a new refresh token issued). On logout, the token must be invalidated server-side — not just deleted from the client. A stolen token from a logged-out session must not be reusable.
Verify: Log out. Attempt to use the old access token. It must return 401. If using JWTs: check the exp claim in the decoded token payload.
11. No Secrets in Responses or Logs
API keys, tokens, passwords, and private keys must never appear in API responses or server logs. Check that your logging framework does not capture request bodies containing passwords or token fields. Check that error handlers do not log full request objects. In CI/CD pipelines, ensure secrets are not echoed to build logs. A secret that appears in a log file is effectively compromised — logs are often stored, indexed, and accessible to a wider audience than production database access.
Verify: Submit a login request with a test password. Search your log files for that password string. It must not appear.
12. Dependency Scanning for Known CVEs
Your API's security posture includes every library it depends on. A single vulnerable dependency — an outdated version of an XML parser, a cryptographic library with a padding oracle, a JWT library with a known algorithm confusion bug — can compromise an otherwise well-secured API. Run dependency scanning as part of your CI/CD pipeline and block deployments when critical CVEs are detected in direct or transitive dependencies.
Verify: Run npm audit, pip-audit, or bundler-audit (depending on your stack). Any critical or high severity finding must be resolved before deployment.
How Shieldome Checks This
Shieldome automatically verifies many of the items on this checklist against your live API endpoints. Each scan checks for:
- HTTPS enforcement and HSTS — verifying redirect behaviour and header presence (checklist items 6)
- Security headers — detecting missing
X-Content-Type-Options,X-Frame-Options, and related headers (item 8) - CORS misconfiguration — flagging wildcard or overly permissive
Access-Control-Allow-Originvalues (item 7) - Server version disclosure — detecting
ServerandX-Powered-Byheaders that reveal technology stack details (item 9) - Cookie security flags — checking for
HttpOnly,Secure, andSameSiteon session tokens (related to item 10) - Injection indicators — probing endpoints for SQL error messages and XSS reflection (items 4 and 5)
- Sensitive path exposure — checking for exposed debug endpoints and admin routes (items 1 and 2)
Items that require authenticated session context — rate limit verification, authorisation testing, log inspection — cannot be fully automated from outside the application. Shieldome covers the observable surface; your development team must verify the internal controls. The combination of automated external scanning and internal checklist review is the most cost-effective way to maintain a secure API.
Frequently Asked Questions
Should I use JWT or session cookies for API authentication?
Both are viable with proper implementation. JWTs are stateless and scale well but cannot be invalidated server-side without a blocklist. Session cookies are stateful, easily revoked, and inherently scoped to the origin — but require server-side session storage that must scale. For most APIs, session cookies with HttpOnly, Secure, and SameSite=Strict are simpler to implement securely than JWTs with proper rotation and revocation logic.
What is the minimum rate limit for a login endpoint?
A common baseline is 5 failed attempts per IP per 15 minutes, with exponential backoff after that. More important than the specific numbers is that the limit is enforced server-side (not just client-side), that it applies per account as well as per IP to prevent distributed attacks, and that valid logins reset the counter. Consider implementing CAPTCHA after a threshold of failures.
Do I need CORS if my API and frontend are on the same domain?
If your API and frontend are served from exactly the same origin (same protocol, domain, and port), same-origin requests are not subject to CORS restrictions and no CORS headers are needed. If your API is at api.example.com and your frontend is at app.example.com, they are different origins and CORS configuration is required. Subdomains are always cross-origin from each other.
How do I handle sensitive data in error messages?
In production, all error responses should return a generic error message with a correlation ID. The correlation ID is logged server-side with the full error details, allowing debugging without exposing internals to the client. Never return stack traces, database error messages, file paths, or query strings in production error responses. Use a middleware error handler that intercepts all exceptions and normalises them before they reach the client.
How often should I run Shieldome scans on my API?
For actively developed APIs, scanning on every significant deployment is ideal — a new route, a dependency update, or an infrastructure change can introduce a regression in seconds. At minimum, schedule a weekly scan in production to catch drift between deployments. Create a free Shieldome account to run your first scan and see exactly which checklist items your API passes and which still need attention.