API Security Best Practices for Production

# 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.