Your API does the real work. The web frontend is a pretty face; the mobile app is a convenient wrapper. But the API handles authentication, serves data, processes transactions, and enforces business logic. And attackers know this. They skip the frontend entirely, fire up Burp Suite or a Python script, and talk directly to your API — probing for missing authorization checks, iterating through object IDs, and hammering authentication endpoints with credential lists. If your API isn’t hardened, the frontend security controls are decorative.
This guide is the practical checklist. Every item is specific, actionable, and testable. The OWASP API Security Top 10 provides the threat model — this guide provides the fixes.
DO / DON’T
DO:
- Enforce authentication on every endpoint — no exceptions without documented justification
- Implement rate limiting at the API gateway and application level
- Validate all input against explicit schemas
- Return only the fields the client needs — never the full database record
- Log every API request with client identity, endpoint, and response code
- Use OAuth 2.0 with PKCE for client-facing authentication
- Version your APIs and deprecate old versions on a published timeline
DON’T:
- Use API keys as the sole authentication mechanism — API keys identify applications, not people
- Trust client-side input validation — it can be bypassed with a single curl command
- Return detailed error messages that reveal internal architecture (stack traces, SQL errors, internal paths)
- Set
Access-Control-Allow-Origin: *unless the API is genuinely public data - Leave introspection enabled on GraphQL endpoints in production
- Expose internal/admin API endpoints on the same domain and port as public APIs
- Rely on obscurity (“nobody knows this endpoint exists”) as a security control
Authentication Enforcement
Every API endpoint must verify the caller’s identity. No exceptions. Internal APIs, admin endpoints, health checks that return sensitive data — all authenticated.
Implementation
- OAuth 2.0 with PKCE for public clients (SPAs, mobile apps). PKCE prevents authorization code interception. RFC 7636 defines the standard.
- Client credentials grant for service-to-service communication. The calling service authenticates with a client ID and secret. Rotate secrets regularly.
- JWT validation must verify all claims: signature (using the correct key), issuer (
iss), audience (aud), expiration (exp), and not-before (nbf). A JWT that’s not fully validated is worse than no JWT — it gives a false sense of security. - Token expiration: Access tokens should be short-lived (15-60 minutes). Use refresh tokens for session continuity. Revoke refresh tokens on logout, password change, and suspicious activity.
# Example: JWT validation checklist
1. Verify signature against the expected signing key
2. Check 'iss' matches your authorization server
3. Check 'aud' includes this API's identifier
4. Check 'exp' is in the future
5. Check 'nbf' is in the past (if present)
6. Check required scopes/permissions for this endpoint
API keys are acceptable for identifying applications (analytics, rate limit tracking) but should never be the sole authentication mechanism for endpoints that access or modify data. API keys are static, easily leaked, and not tied to a specific person’s identity.
NIST SP 800-63B provides authentication assurance level guidance. For APIs handling sensitive data, aim for AAL2 (multi-factor) at minimum.
Authorization — Object and Function Level
Authentication tells you who they are. Authorization tells you what they can do. Most API breaches exploit authorization failures, not authentication bypasses.
Object-Level Authorization (Preventing BOLA)
Every API request that accesses a specific resource must verify that the authenticated identity is authorized to access that specific object. Not just “is this person authenticated?” but “is this person allowed to see order #12345?”
# Pseudocode — every data access must include this check
order = get_order(order_id)
if order.owner_id != authenticated_user.id:
return 403 Forbidden
This must happen on every request, for every object, at the data access layer. Framework middleware or policy engines (OPA, Casbin) can enforce this consistently. Test by accessing objects with different authenticated accounts — if you can see someone else’s data by changing an ID, BOLA exists.
Function-Level Authorization
Admin endpoints require admin authorization. Not just authentication — authorization. A regular account should receive 403 Forbidden when calling /api/admin/users, not a different UI that hides the button. Test every admin and privileged endpoint with non-admin credentials.
Rate Limiting
Rate limiting prevents credential stuffing, brute-force attacks, data scraping, and denial of service. Without it, your API is an all-you-can-eat buffet for automated tools.
Where to Implement
- API Gateway level: Global rate limits, per-client tracking, DDoS protection. Kong, AWS API Gateway, Nginx, and Cloudflare all support this.
- Application level: Endpoint-specific limits. Authentication endpoints should have stricter limits (5-10 attempts per minute per IP) than data retrieval endpoints.
Rate Limiting Strategy
| Endpoint Type | Suggested Limit | Response |
|---|---|---|
| Authentication | 5-10 requests/min per IP | 429 + lockout after threshold |
| Password reset | 3 requests/hour per account | 429 + silent (don’t confirm account existence) |
| Data retrieval | 100-1000 requests/min per token | 429 + Retry-After header |
| Search/filter | 30-60 requests/min per token | 429 + Retry-After header |
| Write operations | 10-50 requests/min per token | 429 + Retry-After header |
Use sliding window counters rather than fixed windows to prevent burst attacks at window boundaries. Return the 429 Too Many Requests status code with a Retry-After header. Log rate limit violations — they’re often indicators of attack activity.
Input Validation
Every parameter, header, and body field must be validated against an explicit schema. Trust nothing from the client.
What to Validate
- Type: Is the field the expected type (string, integer, boolean, array)?
- Length/size: Maximum string length, array size, numeric range. A 10 MB username field is a denial-of-service vector.
- Format: Email addresses, UUIDs, dates — validate against the expected format. Use regex or format validators, not just type checking.
- Allowed values: Enumerations should only accept defined values. A
statusfield that accepts any string is an injection vector. - No unexpected fields: Reject requests with fields not in the schema. This prevents mass assignment attacks where an attacker adds
"role": "admin"to a profile update request.
Schema Validation
Define your API contract with OpenAPI Specification (OAS) and validate every request against it. API gateways and middleware can perform schema validation automatically — rejecting requests that don’t match before they reach your application code.
# OpenAPI example — explicit constraints
components:
schemas:
UserUpdate:
type: object
properties:
name:
type: string
maxLength: 100
email:
type: string
format: email
maxLength: 254
additionalProperties: false # Reject unexpected fields
required: [name, email]
CORS Configuration
Cross-Origin Resource Sharing (CORS) controls which domains can call your API from a browser. Misconfigured CORS is a common finding in API security assessments.
Correct Configuration
- Never use
Access-Control-Allow-Origin: *for APIs that require authentication. The wildcard origin with credentials is a browser exploit waiting to happen. - Allowlist specific origins. Only the domains that should call your API:
https://app.yourdomain.com,https://admin.yourdomain.com. No wildcards. No regex matching that’s too permissive. - Restrict methods.
Access-Control-Allow-Methodsshould list only the HTTP methods your API uses. If you don’t supportDELETE, don’t allow it in CORS. - Restrict headers.
Access-Control-Allow-Headersshould list only the custom headers your API expects. - Set
Access-Control-Max-Age. Cache preflight responses to reduce OPTIONS request overhead. 3600 seconds (1 hour) is reasonable.
API Versioning and Deprecation
Old API versions are attack surface. Every version you maintain is a version you must secure, patch, and monitor.
Versioning Strategy
- URL path versioning (
/api/v1/,/api/v2/) is the most common and most visible. Clients know exactly which version they’re using. - Publish a deprecation policy. “API versions are supported for 18 months after the next major version is released” gives clients a clear migration timeline.
- Monitor deprecated version usage. Before decommissioning, check who’s still calling the old version. Notify them. Give them a deadline. Then shut it down.
- Decommission fully. Don’t leave old versions running “just in case.” Return
410 Gonewith a migration link. Every running API version is an endpoint you must secure.
Logging API Access
You can’t detect API abuse you can’t see. Log everything.
What to Log
Every API request should generate a log entry containing:
- Timestamp (UTC, ISO 8601)
- Client identity (authenticated user/service, API key identifier)
- Source IP (including X-Forwarded-For if behind a proxy)
- HTTP method and endpoint path
- Request parameters (sanitized — no passwords, tokens, or PII in logs)
- Response status code
- Response time
What to Alert On
- Authentication failures exceeding threshold (credential stuffing)
- Rate limit violations (automated abuse)
- 403 responses from a single client (authorization probing)
- Access patterns that iterate through sequential IDs (BOLA exploitation)
- Requests to undocumented endpoints (shadow API discovery)
- Unusual geographic origins for authenticated sessions
Centralize logs with a SIEM or log aggregation platform (ELK, Splunk, Datadog). Set up dashboards for API-specific metrics. OWASP Logging Cheat Sheet provides detailed guidance on security logging.
If It Already Happened
If your API has been exploited — data scraped, accounts compromised, business logic abused — take these steps:
- Identify the scope. Which endpoints were targeted? What data was accessed? Which accounts were affected? Your API access logs are the primary evidence source.
- Block the attacker. Revoke compromised tokens. Block offending IPs or API keys at the gateway. If the attack vector is an authorization bypass, disable the affected endpoint until it’s fixed.
- Fix the root cause. Patch the authorization flaw, add rate limiting, fix the input validation gap. Deploy to production with the endpoint disabled until the fix is verified.
- Notify affected parties. If personal data was accessed, check your breach notification obligations. If API keys were leaked, notify the key holders to rotate them.
- Review and harden. Run through every item in this guide. The breach revealed one vulnerability — there are almost certainly others.
Report to CISA if the compromise affects critical infrastructure or involves significant data exposure.
Your API is your application. The frontend is optional — the API is not. Go through this checklist. Enforce authentication on every endpoint. Add rate limiting. Validate inputs. Log access. The usual suspects are already talking to your API — make sure they don’t get anything they shouldn’t.