# API Security Best Practices for Production
## The Stakes
API breaches exposed 4.2 billion records in 2024. This guide covers essential security practices for production APIs.
## Common API Vulnerabilities
### OWASP Top 10 API Risks
| Rank | Vulnerability | Impact |
|——|————–|——–|
| 1 | Broken Object Level Authorization | Data breach |
| 2 | Broken Authentication | Account takeover |
| 3 | Broken Object Property Level | Data leakage |
| 4 | Unrestricted Resource Consumption | DoS attack |
| 5 | Broken Function Level | Privilege escalation |
## Authentication
### OAuth 2.0 Implementation
“`javascript
// Validate JWT
const jwt = require(‘jsonwebtoken’);
function authenticate(req, res, next) {
const auth = req.headers.authorization;
if (!auth?.startsWith(‘Bearer ‘)) {
return res.status(401).json({ error: ‘No token’ });
}
const token = auth.split(‘ ‘)[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: [‘RS256’],
issuer: ‘your-api’,
audience: ‘your-app’
});
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: ‘Invalid token’ });
}
}
“`
### Token Best Practices
– **Type:** JWT (stateless) or opaque (stateful)
– **Algorithm:** RS256 (asymmetric) > HS256
– **Expiration:** Access 15min, Refresh 7 days
– **Storage:** httpOnly cookies (never localStorage)
### API Keys
“`yaml
# Rate limit by API key
rate_limit:
default: 100/minute
premium: 1000/minute
enterprise: unlimited
“`
## Authorization
### Role-Based Access Control (RBAC)
“`typescript
enum Role {
USER = ‘user’,
MODERATOR = ‘moderator’,
ADMIN = ‘admin’
}
const permissions = {
[Role.USER]: [‘read:own’, ‘update:own’],
[Role.MODERATOR]: [‘read:all’, ‘update:own’, ‘delete:own’],
[Role.ADMIN]: [‘*’]
};
function authorize(…allowedRoles) {
return (req, res, next) => {
const userRole = req.user.role;
if (!allowedRoles.includes(userRole)) {
return res.status(403).json({ error: ‘Forbidden’ });
}
next();
};
}
// Usage
app.get(‘/admin/users’, authenticate, authorize(Role.ADMIN), handler);
“`
### Object-Level Authorization
“`typescript
// ALWAYS verify ownership
async function getDocument(req, res) {
const doc = await Document.findById(req.params.id);
// CRITICAL: Verify user owns this document
if (doc.ownerId !== req.user.id && req.user.role !== ‘admin’) {
return res.status(403).json({ error: ‘Access denied’ });
}
return res.json(doc);
}
“`
## Input Validation
### Schema Validation
“`typescript
const Joi = require(‘joi’);
const createUserSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).max(100).required(),
name: Joi.string().min(1).max(100).required(),
role: Joi.string().valid(‘user’, ‘moderator’).default(‘user’)
});
function validate(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
req.body = value;
next();
};
}
“`
### Sanitization
“`typescript
// Prevent injection
const sanitize = require(‘sanitize-html’);
function sanitizeInput(req, res, next) {
if (req.body.content) {
req.body.content = sanitize(req.body.content, {
allowedTags: [],
allowedAttributes: {}
});
}
next();
}
“`
## Rate Limiting
### Implementation
“`typescript
const rateLimit = require(‘express-rate-limit’);
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP
message: { error: ‘Too many requests’ },
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: true
});
// Different limits for different endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10, // limit login attempts
skipSuccessfulRequests: true
});
app.use(‘/api/’, limiter);
app.post(‘/api/auth/login’, authLimiter);
“`
## HTTPS & Transport Security
### Headers
“`javascript
const helmet = require(‘helmet’);
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: [“‘self'”],
scriptSrc: [“‘self'”],
styleSrc: [“‘self'”, “‘unsafe-inline'”],
imgSrc: [“‘self'”, “data:”, “https:”]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
“`
### TLS Configuration
“`yaml
# nginx.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
“`
## Logging & Monitoring
### What to Log
“`typescript
const logger = require(‘./logger’);
function logSecurityEvent(event) {
logger.warn({
type: ‘security’,
event,
timestamp: new Date().toISOString(),
ip: req.ip,
userId: req.user?.id,
path: req.path,
method: req.method
});
}
// Log these events:
– Failed authentication
– Authorization failures
– Rate limit exceeded
– Suspicious payloads
– Unusual access patterns
“`
### Monitoring Alerts
“`yaml
# Prometheus alert rules
– alert: HighAuthFailures
expr: rate(auth_failures[5m]) > 10
for: 2m
labels:
severity: critical
“`
## CORS Configuration
“`javascript
const cors = require(‘cors’);
app.use(cors({
origin: process.env.ALLOWED_ORIGINS.split(‘,’),
methods: [‘GET’, ‘POST’, ‘PUT’, ‘DELETE’],
allowedHeaders: [‘Content-Type’, ‘Authorization’],
exposedHeaders: [‘X-Total-Count’],
credentials: true,
maxAge: 86400 // 24 hours
}));
“`
## Security Checklist
– [ ] HTTPS enforced everywhere
– [ ] Strong authentication (OAuth 2.0/JWT)
– [ ] Rate limiting enabled
– [ ] Input validation on all endpoints
– [ ] Authorization checks on every resource
– [ ] Security headers (helmet.js)
– [ ] Request logging and monitoring
– [ ] CORS properly configured
– [ ] API keys rotated regularly
– [ ] Dependencies updated
## Conclusion
API security requires defense in depth. No single measure is sufficient—combine authentication, authorization, validation, rate limiting, and monitoring.
**Key takeaway:** Assume every request is malicious until verified. Log everything, validate rigorously.
