APIs are the modern attack surface. Learn how to defend against JWT vulnerabilities, IDOR, injection attacks, and rate limiting bypasses in this comprehensive guide for developers.
APIs are the backbone of modern web and mobile applications. They are also the number one target for attackers. When I conduct web application penetration tests, the UI is rarely where the critical flaws lie; it's almost always in the API layer beneath it.
The OWASP API Security Top 10 exists for a reason. Developers often assume that because an API endpoint isn't directly linked in the UI, nobody will find it. Attackers, however, inspect every request in Burp Suite and fuzz for hidden endpoints relentlessly. Here is a pattern I see constantly in code reviews: strong authentication, but zero authorization checks on the data being requested.
The most common confusion in API security is conflating these two concepts.
Most developers get AuthN right. They implement OAuth2 or JWTs securely. But they completely miss AuthZ, leading to the most devastating API vulnerability: IDOR.
IDOR (often called BOLA - Broken Object Level Authorization) occurs when an application provides direct access to objects based on user-supplied input without verifying permissions.
Consider this API request:
GET /api/v1/invoices/1045 HTTP/1.1
Authorization: Bearer [Valid_JWT_For_User_A]
What happens if User A changes 1045 to 1046? If the server only checks that the JWT is valid (AuthN), it will return User B's invoice. This is an IDOR. The fix is to ensure the requested object actually belongs to the authenticated user context.
Never rely on sequential IDs (like integers). Use UUIDs (v4) for database records exposed via APIs. While UUIDs don't fix the underlying authorization bug, they reduce predictable ID enumeration risk, but they never replace authorization checks.
JWTs are stateless and incredibly popular for API authentication, but they are footguns if misconfigured. The most critical mistakes involve cryptographic validation.
alg: none AttackHistorically, some JWT libraries allowed the header to specify "alg": "none". An attacker could take a valid token, change the payload to make themselves an admin, set the algorithm to "none", and strip the signature. The server would accept it. Always strictly enforce the expected algorithm (e.g., RS256 or HS256) in your backend verification code.
If you use HMAC (HS256), your security relies entirely on a symmetric secret key. If that key is weak (e.g., "secret123"), attackers can crack it offline.
# Cracking a weak JWT secret with hashcat
hashcat -m 16500 jwt.txt rockyou.txt
Use a cryptographically random secret of sufficient strength for HS256, or use RS256 when asymmetric verification is appropriate.
Modern frameworks make it easy to bind incoming JSON directly to database models. This is convenient but dangerous.
Imagine a user updating their profile with this payload:
{
"username": "johndoe",
"email": "[email protected]"
}
An attacker might intercept this and add a field they shouldn't have access to:
{
"username": "johndoe",
"email": "[email protected]",
"is_admin": true
}
If the backend framework blindly merges this JSON into the database model, the attacker just escalated their privileges. Always use explicit DTOs (Data Transfer Objects) or explicitly define which fields are allowed to be updated.
APIs without rate limiting are susceptible to brute force, credential stuffing, abuse, and application-layer resource exhaustion. You should implement rate limiting using an algorithm like the Token Bucket or Leaky Bucket.
When a client exceeds their limit, return a 429 Too Many Requests HTTP status code, and include appropriate rate-limit headers, such as RateLimit or commonly used X-RateLimit-* headers so legitimate clients can back off gracefully:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1609459200
If your rate limiter uses the client's IP address, ensure you are pulling the correct IP from behind your reverse proxy (like Nginx or Cloudflare). Only trust X-Forwarded-For values inserted/forwarded by trusted proxies.
Securing an API requires a defense-in-depth approach. Validate all input, strictly enforce authorization on every single endpoint, never trust client-provided IDs, use strong cryptographic practices for tokens, and implement aggressive rate limiting. These controls significantly reduce the risk of several common API attack classes.