Audience
This guide is for anyone who needs to understand API security fundamentals:
- Backend developers building APIs that handle sensitive data
- Frontend developers integrating with APIs and handling authentication
- Security engineers reviewing API architectures for vulnerabilities
- DevOps engineers configuring API gateways and security controls
- Technical leads making security decisions for their teams
Basic knowledge of HTTP and REST APIs is assumed. If you need a refresher, start with How a REST API Works.
Goal
After reading this guide, you’ll understand:
- What constitutes an API’s attack surface and where vulnerabilities hide
- The OWASP API Security Top 10 threats and how they manifest
- How to implement input validation to prevent injection attacks
- What CORS actually protects (and what it doesn’t)
- Why rate limiting matters and how it works conceptually
- Which security headers every API should implement
You won’t become a penetration tester, but you’ll have the knowledge to build APIs that resist common attacks and pass security reviews.
1. Understanding API Attack Surface
Every API exposes a surface area that attackers can probe. Understanding this surface is the first step to defending it.
What is Attack Surface?
The attack surface is the sum of all points where an attacker can try to enter or extract data from your system. For APIs, this includes:
graph TB
subgraph "API Attack Surface"
A[Endpoints] --> A1["/users, /orders, /admin"]
B[Data] --> B1["Request bodies, query params, headers"]
C[Authentication] --> C1["Tokens, sessions, API keys"]
D[Business Logic] --> D1["Workflows, state machines"]
E[Infrastructure] --> E1["Servers, databases, caches"]
end
style A fill:#ffcdd2
style B fill:#fff9c4
style C fill:#c8e6c9
style D fill:#bbdefb
style E fill:#e1bee7The Three Dimensions of API Risk
1. Endpoints (What you expose)
Every endpoint you create is a potential entry point. Consider:
- How many endpoints does your API have?
- Are there hidden or undocumented endpoints?
- Do you have debug or admin endpoints in production?
2. Data (What flows through)
Data enters your API through:
- Request bodies (JSON, XML, form data)
- Query parameters (
?search=...&limit=...) - Path parameters (
/users/{id}) - Headers (Authorization, custom headers)
- Cookies
Each input channel is a potential injection vector.
3. Authentication/Authorization (Who can access what)
The most common API security failures involve:
- Broken authentication (weak tokens, missing validation)
- Broken authorization (accessing other users’ data)
- Excessive data exposure (returning more than needed)
Real-World Example: The Attack Surface of a Banking API
Consider a simple banking API:
POST /auth/login # Authentication
POST /auth/refresh # Token refresh
GET /accounts # List user accounts
GET /accounts/{id} # Account details
POST /transfers # Create transfer
GET /transfers/{id} # Transfer status
GET /statements/{year} # Annual statementsAttack surface analysis:
| Component | Risk | Example Attack |
|---|---|---|
/auth/login | Credential stuffing, brute force | Try millions of password combinations |
/accounts/{id} | IDOR (Insecure Direct Object Reference) | Change id to access other accounts |
/transfers | Business logic abuse | Negative transfer amounts |
| Query params | Injection | ?year=2024; DROP TABLE accounts |
| JWT tokens | Token forgery, algorithm confusion | Change alg to none |
Key insight: Security isn’t just about one endpoint. It’s about understanding how all pieces interact and where assumptions break down.
2. Common Threats: OWASP API Security Top 10
The OWASP API Security Top 10 catalogs the most critical API vulnerabilities. Let’s examine each one.
API1: Broken Object Level Authorization (BOLA)
The problem: Users can access resources belonging to other users by manipulating object IDs.
Example:
# User A requests their order
GET /orders/12345
Authorization: Bearer token_user_a
# User A changes the ID to access User B's order
GET /orders/12346
Authorization: Bearer token_user_a # Same token, different order!Why it happens: The API trusts the object ID without verifying the user owns that resource.
Prevention:
- Always verify resource ownership before returning data
- Use indirect references (user-specific IDs) instead of database IDs
- Implement authorization checks at the data layer, not just the API layer
API2: Broken Authentication
The problem: Weak authentication mechanisms allow attackers to impersonate users.
Common failures:
- Weak password policies
- No rate limiting on login attempts
- Tokens that don’t expire
- Credentials in URLs (query strings)
- Missing multi-factor authentication for sensitive operations
API3: Broken Object Property Level Authorization
The problem: Users can modify object properties they shouldn’t have access to.
Example:
// User tries to update their profile
PUT /users/me
{
"name": "Alice",
"email": "[email protected]",
"role": "admin", // Shouldn't be allowed!
"verified": true // Shouldn't be allowed!
}Prevention: Explicitly define which fields users can modify. Never blindly accept all fields from the request.
API4: Unrestricted Resource Consumption
The problem: APIs don’t limit how many resources a user can consume.
Impacts:
- Denial of Service (DoS)
- Unexpected cloud bills
- Database exhaustion
Examples:
- No pagination limits (
GET /users?limit=1000000) - Expensive operations without throttling (complex searches)
- File uploads without size limits
API5: Broken Function Level Authorization
The problem: Users can access administrative functions they shouldn’t.
Example:
# Regular user discovers admin endpoint
DELETE /admin/users/12345
Authorization: Bearer regular_user_token
# API doesn't verify the user has admin privilegesPrevention: Implement role-based access control (RBAC) and verify permissions for every function.
API6: Unrestricted Access to Sensitive Business Flows
The problem: Attackers abuse legitimate features for malicious purposes.
Examples:
- Creating thousands of accounts to abuse free trials
- Automating purchase flows to scalp inventory
- Scraping data at scale despite terms of service
API7: Server Side Request Forgery (SSRF)
The problem: The API fetches resources from user-supplied URLs, allowing access to internal systems.
Example:
// Legitimate request
POST /fetch-metadata
{
"url": "https://example.com/image.png"
}
// Malicious request
POST /fetch-metadata
{
"url": "http://169.254.169.254/latest/meta-data/" // AWS metadata!
}API8: Security Misconfiguration
The problem: Default configurations, unnecessary features, or missing security headers.
Examples:
- Debug mode enabled in production
- Default credentials unchanged
- Unnecessary HTTP methods enabled (TRACE, OPTIONS)
- Missing security headers (see Section 6)
- Verbose error messages exposing internal details
API9: Improper Inventory Management
The problem: Organizations lose track of their APIs, leaving old versions exposed.
Common issues:
- Old API versions still accessible (
/v1/alongside/v3/) - Development/staging APIs exposed to internet
- Undocumented endpoints created by developers
- Shadow APIs created by other teams
API10: Unsafe Consumption of APIs
The problem: Your API blindly trusts data from third-party APIs.
Example: Your API calls a partner API and inserts the response directly into your database without validation, allowing injection attacks through the third party.
3. Input Validation: Your First Line of Defense
Every piece of data entering your API is potentially malicious. Input validation is the practice of verifying that data meets expected formats before processing.
The Golden Rule
Never trust client input. Validate everything.
This includes:
- Request bodies
- Query parameters
- Path parameters
- Headers
- File uploads
Types of Validation
1. Type validation: Is it the expected data type?
// Expected: number
// Received: "123; DROP TABLE users"
const userId = parseInt(req.params.id);
if (isNaN(userId)) {
return res.status(400).json({ error: "Invalid user ID" });
}2. Format validation: Does it match the expected pattern?
// Email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({ error: "Invalid email format" });
}3. Range validation: Is the value within acceptable bounds?
// [Pagination](https://reference.apios.info/terms/pagination/) limit validation
const limit = Math.min(Math.max(parseInt(req.query.limit) || 20, 1), 100);
// limit will be between 1 and 100
4. Business logic validation: Does the value make sense?
// Transfer amount validation
if (amount <= 0) {
return res.status(400).json({ error: "Amount must be positive" });
}
if (amount > account.balance) {
return res.status(400).json({ error: "Insufficient funds" });
}Schema Validation
Instead of validating each field manually, use schema validators like JSON Schema, Joi, Zod, or Yup:
import { z } from 'zod';
const TransferSchema = z.object({
fromAccount: z.string().uuid(),
toAccount: z.string().uuid(),
amount: z.number().positive().max(1000000),
currency: z.enum(['USD', 'EUR', 'GBP']),
description: z.string().max(200).optional()
});
// Usage
try {
const validData = TransferSchema.parse(req.body);
// Process validData safely
} catch (error) {
return res.status(400).json({ error: error.errors });
}Output Encoding
Validation prevents bad data from entering. Output encoding prevents bad data from causing harm when leaving:
// Don't return raw user input in HTML responses
// Bad:
res.send(`<p>Welcome, ${username}</p>`);
// Good:
import { escape } from 'html-escaper';
res.send(`<p>Welcome, ${escape(username)}</p>`);What Validation Won’t Catch
Validation stops malformed data, but not:
- Authorized users doing unauthorized things (BOLA)
- Valid data used for malicious purposes (business logic abuse)
- Attacks through other channels (social engineering)
Validation is necessary but not sufficient. Defense requires multiple layers.
4. CORS: What It Actually Protects
CORS (Cross-Origin Resource Sharing) is one of the most misunderstood security mechanisms. Let’s clarify what it does and doesn’t do.
The Same-Origin Policy
Browsers enforce the Same-Origin Policy: scripts on one origin can’t freely access resources from another origin.
Origin = Protocol + Domain + Port
| URL | Origin |
|---|---|
https://api.example.com/users | https://api.example.com |
https://api.example.com:443/users | https://api.example.com (443 is default) |
http://api.example.com/users | http://api.example.com (different protocol) |
https://app.example.com/ | https://app.example.com (different subdomain) |
What CORS Actually Does
CORS is a relaxation of the Same-Origin Policy. It lets servers specify which origins can access their resources.
sequenceDiagram
participant Browser
participant Frontend as app.example.com
participant API as api.example.com
Browser->>Frontend: Load page
Frontend->>Browser: JavaScript wants to call API
Browser->>API: OPTIONS /users (Preflight)
Note over Browser,API: "Can app.example.com access this?"
API->>Browser: Access-Control-Allow-Origin: app.example.com
Browser->>API: GET /users
API->>Browser: Response data
Browser->>Frontend: Data deliveredCORS Headers Explained
Server response headers:
# Which origins can access the resource
Access-Control-Allow-Origin: https://app.example.com
# Which HTTP methods are allowed
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Which headers the client can send
Access-Control-Allow-Headers: Content-Type, Authorization
# Whether to include credentials (cookies, auth headers)
Access-Control-Allow-Credentials: true
# How long to cache the preflight response (seconds)
Access-Control-Max-Age: 86400The Preflight Request
For “non-simple” requests (PUT, DELETE, custom headers, JSON body), browsers send a preflight OPTIONS request first:
OPTIONS /users [HTTP/1.1](https://reference.apios.info/terms/http-1-1/)
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, AuthorizationThe server must respond with appropriate CORS headers, or the browser blocks the actual request.
Common CORS Mistakes
Mistake 1: Allowing all origins
# DANGEROUS: Any website can make requests!
Access-Control-Allow-Origin: *Problem: Any website can call your API from their JavaScript. Combined with Allow-Credentials: true, attackers can impersonate logged-in users.
Mistake 2: Reflecting the Origin header
# DANGEROUS: Echoing back whatever origin is sent
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = originProblem: This is equivalent to * but works with credentials, making it even more dangerous.
Mistake 3: Trusting null origin
# DANGEROUS: null origin can be spoofed
Access-Control-Allow-Origin: nullProblem: Attackers can send requests from sandboxed iframes with origin: null.
What CORS Doesn’t Protect
CORS only affects browsers. It doesn’t protect against:
- Server-to-server requests (curl, Postman, backend services)
- Mobile apps
- Anyone who copies the request from browser DevTools
CORS is not authentication. It’s a browser policy that prevents unwitting users from being exploited by malicious websites.
Correct CORS Configuration
const cors = require('cors');
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com'
];
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (mobile apps, curl)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));5. Rate Limiting: Protecting Against Abuse
Rate limiting restricts how many requests a client can make in a given time period. It’s essential for preventing abuse and ensuring fair resource usage.
Why Rate Limiting Matters
Without rate limiting, attackers can:
- Brute force authentication: Try millions of passwords
- Denial of Service: Overwhelm your servers with requests
- Scrape data: Download your entire database
- Exploit business logic: Create unlimited accounts, abuse promotions
- Generate massive bills: If you pay per API call to third parties
Rate Limiting Concepts
graph LR
subgraph "Rate Limit Window"
A[Request 1] --> B[Request 2]
B --> C[Request 3]
C --> D[...]
D --> E[Request 100]
E --> F[Request 101 - BLOCKED]
end
G[Window Resets] --> A
style F fill:#ffcdd2
style G fill:#c8e6c9Key concepts:
| Term | Description | Example |
|---|---|---|
| Limit | Maximum requests allowed | 100 requests |
| Window | Time period for counting | per minute |
| Identifier | How to identify the client | IP address, API key, user ID |
| Quota | The remaining requests | 73/100 remaining |
Common Rate Limiting Strategies
1. Fixed Window
- Count requests in fixed time intervals
- Simple but can allow bursts at window edges
- Example: 100 requests per minute, resets at :00
2. Sliding Window
- Smooths out the fixed window problem
- More complex to implement
- Example: 100 requests in any 60-second period
3. Token Bucket
- Tokens replenish at a fixed rate
- Allows bursts up to bucket size
- Example: 10 tokens/second, bucket holds 100
4. Leaky Bucket
- Requests processed at a fixed rate
- Excess requests queue or drop
- Smoothest traffic pattern
Rate Limit Headers
Standard headers to communicate rate limit status:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1641024000
# When limit exceeded
HTTP/1.1 [429 Too Many Requests](https://reference.apios.info/terms/429-too-many-requests/)
Retry-After: 60What to Rate Limit
| Resource | Why | Example Limit |
|---|---|---|
| Login attempts | Prevent brute force | 5 per minute per IP |
| Password reset | Prevent enumeration | 3 per hour per email |
| API endpoints | General abuse prevention | 1000 per hour per user |
| Expensive operations | Prevent DoS | 10 per minute |
| File uploads | Storage abuse | 100 MB per day |
Choosing Identifiers
How you identify clients affects security:
| Identifier | Pros | Cons |
|---|---|---|
| IP address | No auth required | Shared IPs, VPNs, proxies |
| API key | Clear accountability | Key can be stolen |
| User ID | Accurate per-user | Requires authentication |
| Combination | Most accurate | Complex to implement |
Best practice: Use multiple identifiers. Rate limit by IP for unauthenticated endpoints, by user ID for authenticated ones.
Handling Rate Limit Exceeded
Return a helpful response:
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please retry after 60 seconds.",
"retryAfter": 60,
"limit": 100,
"window": "1 minute",
"documentation": "https://api.example.com/docs/rate-limits"
}6. Security Headers: Essential HTTP Headers
HTTP headers provide a simple way to enable browser security features. Every API should implement these.
Essential Security Headers
1. Strict-Transport-Security (HSTS)
Forces browsers to use HTTPS, preventing downgrade attacks.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadmax-age: How long to remember the policy (1 year recommended)includeSubDomains: Apply to all subdomainspreload: Submit to browser preload lists
2. Content-Security-Policy (CSP)
Controls which resources the browser can load. Less relevant for JSON APIs, but critical if your API returns HTML.
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'3. X-Content-Type-Options
Prevents browsers from MIME-sniffing responses.
X-Content-Type-Options: nosniffWhy it matters: Without this, browsers might interpret JSON as HTML, enabling XSS attacks.
4. X-Frame-Options
Prevents your pages from being embedded in iframes (clickjacking protection).
X-Frame-Options: DENY5. Cache-Control
Controls caching behavior. Critical for sensitive data.
# For sensitive responses
Cache-Control: no-store, no-cache, must-revalidate, private
# For public, cacheable responses
Cache-Control: public, max-age=36006. Content-Type
Always set the correct content type to prevent MIME confusion.
Content-Type: application/json; charset=utf-8Complete Security Headers Example
const helmet = require('helmet');
// Apply security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none'"],
frameAncestors: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
noSniff: true,
frameguard: { action: 'deny' }
}));
// Custom headers for API responses
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'no-store');
res.setHeader('Pragma', 'no-cache');
next();
});Headers to Avoid
Remove these headers as they expose information:
# Remove - reveals server technology
Server: Apache/2.4.41 (Ubuntu)
X-Powered-By: Express
# Remove - reveals versions
X-AspNet-Version: 4.0.303197. Putting It All Together: Security Checklist
Here’s a practical checklist for securing your API:
Authentication & Authorization
- Use strong authentication (OAuth 2.0, JWT with proper validation)
- Implement authorization checks for every endpoint
- Verify resource ownership, not just authentication
- Use short-lived tokens with refresh mechanisms
- Implement multi-factor authentication for sensitive operations
Input & Output
- Validate all input (type, format, range, business logic)
- Use schema validation libraries
- Sanitize/encode output to prevent injection
- Return consistent error formats (JSON, not HTML)
- Don’t expose internal details in error messages
Rate Limiting & Abuse Prevention
- Rate limit all endpoints (especially auth)
- Use appropriate identifiers (IP, user ID, API key)
- Return helpful 429 responses with Retry-After
- Monitor for abuse patterns
Transport Security
- Use HTTPS everywhere (no HTTP)
- Implement HSTS header
- Use secure cookies (Secure, HttpOnly, SameSite)
- Keep TLS configuration up to date
Headers
- Set all security headers (HSTS, CSP, X-Content-Type-Options)
- Remove information disclosure headers (Server, X-Powered-By)
- Use correct Content-Type
- Set appropriate Cache-Control
CORS
- Whitelist specific origins (no wildcard with credentials)
- Don’t reflect Origin header
- Limit allowed methods and headers
8. What’s Next: Defensive API Security Course
This guide covered the fundamentals of API security: understanding attack surface, common threats, and essential controls. But there’s much more to learn:
Topics Covered in the Course
The Defensive API Security course dives deep into:
- Hands-on threat modeling: How to systematically identify vulnerabilities in your APIs
- Security testing techniques: Manual testing, automated scanning, fuzzing
- WAF and API Gateway configuration: Real-world setup guides
- Authentication deep-dive: OAuth 2.0 flows, JWT security, session management
- Advanced rate limiting: Implementing token bucket, distributed rate limiting
- Security monitoring: Logging, alerting, incident response
- Compliance requirements: GDPR, PCI-DSS, SOC 2 implications for APIs
Why a Course?
Security requires practice, not just knowledge. The course includes:
- Interactive labs where you attack and defend APIs
- Real-world case studies from security breaches
- Configuration templates for popular frameworks
- Code review exercises
- Assessment tools and checklists
Related Vocabulary Terms
Deepen your understanding with these related terms:
- CORS - Cross-Origin Resource Sharing mechanism
- Rate Limiting - Controlling request frequency
- API Security - Overview of API security concerns
- OWASP - Open Web Application Security Project
- Input Validation - Validating incoming data
- Authentication - Verifying identity
- Authorization - Verifying permissions
- JWT - JSON Web Tokens
- API Key - Simple API authentication
- HTTP Status Codes - Understanding response codes