APIs are the connective tissue of modern software β they link mobile apps to backends, connect microservices to each other, and expose business data to third-party integrations. They are also the primary attack vector. OWASP's API Security Top 10, last revised in 2023, documents the vulnerability categories that appear most consistently in real-world API breaches: broken object-level authorization, broken authentication, excessive data exposure, and a handful of others that are well-understood but still routinely missed in production. This article provides an implementation-focused checklist covering the controls that matter most, with practical guidance on how to actually build them in.
Image: File:API structure.png β Oliviameza3 (CC BY-SA 4.0), via Wikimedia Commons
Authentication: Getting the Foundation Right
Authentication is where API security begins, and where it most often goes wrong. The two dominant patterns in 2026 are OAuth 2.0 with PKCE (Proof Key for Code Exchange) for delegated authorization, and API keys for machine-to-machine service authentication. Neither is inherently superior β the choice depends on your use case.
OAuth 2.0 + PKCE is the right choice when a user is delegating access to a third-party application. PKCE prevents authorization code interception attacks that affect the older implicit flow. If you are building a public API that third parties will call on behalf of users, implement OAuth 2.0 with PKCE and avoid the deprecated implicit flow entirely. Libraries like passport-oauth2 (Node.js) or authlib (Python) handle the protocol correctly β do not implement the OAuth handshake from scratch.
JSON Web Tokens (JWT) are commonly used as bearer tokens after authentication. They are convenient but introduce specific risks. Key mistakes to avoid: verifying JWT signatures with alg: none (always specify the allowed algorithm explicitly), using symmetric secrets that are too short (minimum 256 bits for HS256), and not validating the aud (audience) claim β which means a token issued for service A will be accepted by service B. Set short expiry times (15 minutes for access tokens) and implement token rotation for refresh tokens.
API keys for service-to-service authentication should be rotatable, scoped to minimum necessary permissions, and stored in a secrets manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) β never in source code or environment variables committed to version control. Implement key rotation without downtime by supporting a brief dual-key acceptance window.
Authorization: The Gap Most Developers Underestimate
Authentication confirms who you are; authorization determines what you are allowed to do. OWASP ranks Broken Object Level Authorization (BOLA) as the top API vulnerability because it is so easy to introduce and so hard to detect automatically. BOLA occurs when an API endpoint accepts a resource ID in the request and returns or modifies that resource without verifying that the authenticated user actually owns or has access to it.
The canonical vulnerable pattern:
GET /api/invoices/7842
Authorization: Bearer <user_A_token>
# Returns user B's invoice if the server doesn't check ownership
The fix requires an explicit authorization check on every resource access, not just at the route level. This means: extract the user ID from the authenticated token, then query the database with a condition that ties the resource to that user. Centralize this logic in a service layer rather than duplicating it across route handlers. Function-level authorization (BFLA) β where one user can call admin endpoints β requires the same discipline: check the role or permission scope on every sensitive operation, not just on the route's middleware.
Input Validation and Injection Prevention
Every API endpoint that accepts external input is a potential injection vector. The practical controls:
- Schema validation: Define and enforce a strict schema for every request body, query parameter, and path variable. Libraries like Zod (TypeScript), Pydantic (Python), and Joi (Node.js) make this straightforward. Reject requests that do not conform β do not attempt to sanitize malformed input.
- Parameterized queries: Never construct SQL queries with string concatenation. Use prepared statements or an ORM that handles parameterization. This is not negotiable.
- Command injection: If your API ever calls shell commands (uncommon but it happens in certain domains), use argument arrays rather than shell strings and never interpolate user input into shell commands.
- Content-type enforcement: Set
Content-Type: application/jsonand reject requests with unexpected content types. Do not attempt to parse XML or multipart data unless the endpoint explicitly requires it β each parser increases attack surface.
Rate Limiting and Abuse Prevention
| Control | Purpose | Implementation Notes |
|---|---|---|
| Request rate limiting | Brute force and DoS prevention | Limit per IP and per authenticated user; use sliding window algorithms |
| Login attempt throttling | Credential stuffing prevention | Exponential backoff after 5 failures; CAPTCHA or MFA after 10 |
| Query complexity limits (GraphQL) | Prevent DoS via deeply nested queries | Set max depth and max complexity score per query |
| Payload size limits | Memory exhaustion prevention | Enforce at the reverse proxy level (nginx, API gateway) |
| Response filtering | Excessive data exposure | Return only fields the consumer needs; never return full DB rows |
Rate limiting should be implemented at multiple layers: at the API gateway or reverse proxy for broad protection, and at the application layer for fine-grained controls. Redis-backed token bucket or sliding window counters work well at scale. Always return Retry-After headers when rate limiting kicks in β this is both good UX and required for standards compliance.
Transport Security and Headers
TLS 1.2 is the minimum acceptable version; TLS 1.3 should be preferred for new deployments. Enforce HTTPS-only by redirecting HTTP to HTTPS at the load balancer level and setting Strict-Transport-Security: max-age=31536000; includeSubDomains. For APIs consumed by browsers, configure CORS explicitly β never use a wildcard origin (Access-Control-Allow-Origin: *) on endpoints that accept authentication credentials.
Required security headers for API responses:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'none'
Cache-Control: no-store # For responses containing sensitive data
Image: File:Cyber Security at the Ministry of Defence MOD 45153617.jpg β Harland Quarrington (OGL v1.0), via Wikimedia Commons
Logging, Monitoring, and Incident Response
An API that is not monitored is effectively undefended β breaches often go undetected for weeks or months when logging is insufficient. At minimum, log: authentication events (success and failure), authorization failures, rate limit triggers, and any 5xx errors. Do not log request bodies that may contain passwords or tokens. Ship logs to a centralized system (ELK stack, Datadog, Grafana Loki) with alerting on anomalies: sudden spikes in 401/403 responses, unusual geographic access patterns, or enumeration behavior (sequential object ID access).
Implement a documented incident response procedure that covers: how to revoke compromised tokens, how to rotate API keys without service interruption, and who owns the security response process. The best technical controls are undermined when the human response process is undefined.
Frequently Asked Questions
Should I build my own authentication system or use an identity provider?
Use an established identity provider (Auth0, AWS Cognito, Azure AD B2C, Keycloak) unless you have strong reasons not to. Implementing OAuth 2.0 correctly from scratch requires handling dozens of edge cases β token revocation, refresh token rotation, PKCE, secure storage β that identity providers have already solved and continue to maintain. Rolling your own authentication is one of the most common sources of serious API security vulnerabilities. The engineering cost of a custom implementation almost always outweighs the integration cost of a proven provider.
What is the difference between BOLA and BFLA?
Broken Object Level Authorization (BOLA) means a user can access or modify another user's specific resource by supplying a different object ID β for example, changing /invoices/1001 to /invoices/1002 and getting another customer's data. Broken Function Level Authorization (BFLA) means a regular user can call administrative or privileged endpoints they should not have access to. Both require explicit authorization checks β BOLA at the data-row level, BFLA at the function/role level β and both are commonly found in audits of production APIs.
How should I handle API versioning to avoid breaking security controls?
Version your API explicitly (v1, v2) and apply security controls at the gateway or middleware level so they apply uniformly across all versions. A common mistake is applying new security controls to v2 while leaving deprecated v1 endpoints exposed without equivalent protection. When deprecating an API version, set a firm sunset date, enforce it, and ensure monitoring covers the deprecated endpoints until they are fully decommissioned. Attackers actively probe for older, less-secured versions of APIs.
Bottom Line
API security is not a feature β it is a set of properties that must be designed in from the start and maintained through the full lifecycle of your service. The highest-impact controls are authentication (use established identity providers, validate JWTs completely), authorization (check ownership at the data row level, not just the route), input validation (strict schemas, parameterized queries), and rate limiting (at both the gateway and application layers). We recommend auditing every API endpoint against the OWASP API Security Top 10 checklist before launch, automating security header checks in your CI pipeline, and logging authentication and authorization events to a centralized monitoring system. The controls are well-understood; what separates secure APIs from vulnerable ones is consistent implementation and ongoing verification.
Sources & References:
OWASP API Security Top 10 (2023 edition)
RFC 7519 β JSON Web Token (JWT)
RFC 7636 β Proof Key for Code Exchange (PKCE)
RFC 6749 β The OAuth 2.0 Authorization Framework
PortSwigger Web Security Academy β API Security Testing.
Disclaimer: This article is for informational purposes only. Technology landscapes change rapidly; verify information with official sources before making technical decisions.