Table of Contents
- Why API Security Is Non-Negotiable
- 1. Authenticate Every Request
- 2. Use HTTPS Everywhere — No Exceptions
- 3. Implement Rate Limiting and Throttling
- 4. Validate and Sanitize All Input
- 5. Return the Minimum Data Necessary
- 6. Version Your API
- 7. Log Everything — Then Monitor It
- 8. Know the OWASP API Top 10
- Quick Security Checklist
Why API Security Is Non-Negotiable
APIs are the backbone of modern software. Your mobile app talks to an API. Your SaaS dashboard reads from an API. Your payment processor, your authentication layer, your third-party integrations — all APIs. That makes them the single most targeted attack surface in 2026.
The Verizon Data Breach Report consistently finds that web application attacks — most of which target APIs — account for the majority of confirmed data breaches. A single misconfigured endpoint can expose millions of user records, allow account takeover, or leak business-critical data.
The good news: most API attacks exploit well-known, preventable mistakes. This guide covers the practices that close those gaps.
1. Authenticate Every Request
Never trust an unauthenticated request. Every API endpoint should require proof of identity unless you have a deliberate, documented reason to make it public.
Use industry-standard authentication
- OAuth 2.0 + OpenID Connect — the gold standard for delegated authorization. Use it when users need to grant third-party apps access to their data.
- JWT (JSON Web Tokens) — compact, stateless tokens for API-to-API or client-to-server authentication. Keep them short-lived (15 minutes to 1 hour).
- API Keys — acceptable for server-to-server communication. Never expose them client-side.
JWT security checklist
✓ Sign with RS256 (asymmetric) not HS256 where possible
✓ Validate the "alg" header — reject "none"
✓ Set a short expiry (exp claim)
✓ Validate issuer (iss) and audience (aud)
✓ Store on the server side in httpOnly cookies, not localStorage
✓ Implement refresh token rotation
Common authentication mistakes
- Accepting expired tokens because expiry validation was skipped
- Using symmetric secrets that are too short or reused across environments
- Sending API keys in URL query strings (they end up in server logs)
- Not revoking tokens on logout
2. Use HTTPS Everywhere — No Exceptions
All API traffic must travel over TLS 1.2 or TLS 1.3. HTTP is unacceptable — credentials, tokens, and sensitive data transmitted over plain HTTP can be intercepted trivially with a packet sniffer.
Enforce it:
- Configure your server to reject non-HTTPS connections entirely — redirect or drop them
- Set
Strict-Transport-Security(HSTS) headers so browsers remember to use HTTPS - Use TLS 1.3 where possible — it is faster and more secure than 1.2
- Pin certificates in mobile clients if your threat model warrants it
Check your TLS configuration with SSL Labs (ssllabs.com/ssltest) before going to production. Aim for an A or A+ rating.
3. Implement Rate Limiting and Throttling
Without rate limiting, your API is open to:
- Brute force attacks — automated credential stuffing against login endpoints
- Denial of service — a single abusive client consuming all your resources
- Enumeration attacks — bots cycling through IDs or emails to harvest data
- Scraping — competitors harvesting your content at machine speed
How to implement it
Rate limit by IP address AND by authenticated user ID. IP-only limits are trivially bypassed with IP rotation.
# Example: FastAPI with slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.get("/api/login")
@limiter.limit("5/minute") # 5 login attempts per minute per IP
async def login(request: Request, ...):
...
Recommended limits by endpoint type:
| Endpoint Type | Suggested Limit |
|---|---|
| Login / auth | 5–10 / minute |
| Password reset | 3 / hour |
| General read | 100–300 / minute |
| Write / POST | 30–60 / minute |
| Search | 20–50 / minute |
Return 429 Too Many Requests with a Retry-After header so clients know when to back off. Never silently drop requests — log them.
4. Validate and Sanitize All Input
Every piece of data that enters your API from the outside world is untrusted. Treat it that way.
What to validate
- Type — is this actually a number, not a string containing
'; DROP TABLE users;--? - Range — is
age: 9999a valid age? - Length — is a 50,000-character “username” ever legitimate?
- Format — does this match the expected pattern (email, UUID, date)?
- Allowed values — if the field should be one of
["admin", "user", "guest"], reject anything else
Use a schema validation library
Never write manual validation logic for complex objects. Use battle-tested libraries:
- Python: Pydantic, marshmallow
- Node.js: Zod, Joi, Yup
- Go: go-playground/validator
- Java: Hibernate Validator (Bean Validation)
# Pydantic example — invalid data is rejected before it touches your logic
from pydantic import BaseModel, EmailStr, Field
class CreateUserRequest(BaseModel):
email: EmailStr
username: str = Field(min_length=3, max_length=32, pattern=r'^[a-zA-Z0-9_]+$')
age: int = Field(ge=13, le=120)
SQL injection is still killing APIs
Parameterised queries and ORMs protect you from SQL injection. Raw string concatenation does not:
# WRONG — never do this
query = f"SELECT * FROM users WHERE email = '{user_input}'"
# RIGHT — parameterised
cursor.execute("SELECT * FROM users WHERE email = %s", (user_input,))
5. Return the Minimum Data Necessary
Mass assignment and over-fetching are two of the most common API vulnerabilities.
Mass assignment happens when your API blindly binds request data to a model:
# WRONG — attacker can set is_admin=true in the request body
user = User(**request.json())
# RIGHT — explicitly select which fields are writable
user = User(
name=data["name"],
email=data["email"]
)
Over-fetching happens when your API returns entire objects when only a few fields are needed. If your /users/me endpoint returns password_hash, stripe_customer_id, and internal_notes alongside the user’s name — that is a data exposure risk.
Explicitly define your response schemas and serialise only what the client needs.
6. Version Your API
Versioning keeps you from breaking clients while giving you the freedom to make security improvements. If you discover a vulnerability in your current API design, you can fix it in v2 without forcing immediate changes on all consumers.
Use URL versioning (/api/v1/, /api/v2/) — it is explicit and easy to route:
GET /api/v1/users/123 ← old clients still work
GET /api/v2/users/123 ← new, more secure design
Set a clear deprecation timeline and communicate it to API consumers. Do not leave insecure old versions running indefinitely.
7. Log Everything — Then Monitor It
Logs are useless if no one reads them. Set up structured logging and active monitoring from day one.
Log every request (at minimum):
- Timestamp
- Request method and path
- Response status code
- Authenticated user ID (if any)
- IP address
- Response time
Alert on:
- Sudden spike in
401or403responses (credential stuffing or enumeration) - Spike in
500errors (something broke — possibly under attack) - Requests from a single IP hitting many different endpoints quickly (scanning)
- Requests for endpoints that do not exist (reconnaissance)
Tools worth knowing: Datadog, Grafana + Loki, ELK Stack, Sentry, AWS CloudWatch.
8. Know the OWASP API Top 10
The OWASP API Security Top 10 is the definitive list of the most critical API risks. Every API developer should know it:
| # | Risk | What It Means |
|---|---|---|
| 1 | Broken Object Level Authorization (BOLA) | Users accessing other users’ objects via ID manipulation |
| 2 | Broken Authentication | Weak tokens, missing expiry, no brute-force protection |
| 3 | Broken Object Property Level Authorization | Returning or accepting fields a user should not touch |
| 4 | Unrestricted Resource Consumption | No rate limits — API is exhausted by a single client |
| 5 | Broken Function Level Authorization | Regular users accessing admin endpoints |
| 6 | Unrestricted Access to Sensitive Business Flows | Bots exploiting checkout, registration, or voting flows |
| 7 | Server Side Request Forgery (SSRF) | API fetches URLs from user input — attacker points it inward |
| 8 | Security Misconfiguration | Default credentials, verbose errors, open CORS |
| 9 | Improper Inventory Management | Forgotten debug/staging endpoints exposed in production |
| 10 | Unsafe Consumption of APIs | Trusting third-party API responses without validation |
BOLA (number one) is the most commonly exploited. The fix: always check that the authenticated user owns the resource they are requesting — never trust the ID in the request alone.
Quick Security Checklist
Before you ship any API to production, run through this list:
Authentication
✓ Every endpoint requires auth (or is explicitly documented as public)
✓ Tokens are short-lived and rotated
✓ Secrets are in environment variables, not source code
Transport
✓ HTTPS enforced — HTTP rejected
✓ TLS 1.2+ only, TLS 1.3 preferred
✓ HSTS header set
Input / Output
✓ All input validated with a schema library
✓ Parameterised queries used everywhere
✓ Responses expose only necessary fields
Access Control
✓ Object-level ownership checked on every read/write
✓ Admin and privileged endpoints require explicit role check
✓ CORS configured to specific origins only
Operational
✓ Rate limiting on all public endpoints
✓ Structured logging in place
✓ Error messages do not expose stack traces to clients
✓ API versioned
Security is not a feature you bolt on at the end — it is a design discipline you apply from the first line of code. Start with these practices and you will avoid the vulnerabilities that compromise most APIs in the wild.