Table of Contents
Why API Security Matters
APIs are the backbone of modern applications. They connect mobile apps, web frontends, microservices, and third-party integrations. But they are also the #1 attack vector for data breaches.
Alarming Statistics
- 91% of web applications have API vulnerabilities (Salt Security)
- API attacks increased 681% in 2021 (Salt Labs)
- Average cost of API breach: $4.45 million (IBM)
OWASP API Security Top 10 (2023)
| # | Vulnerability | Description |
|---|---|---|
| 1 | Broken Object Level Auth | Accessing other users' data by changing IDs |
| 2 | Broken Authentication | Weak tokens, no expiration, credential stuffing |
| 3 | Broken Object Property Auth | Mass assignment, exposing sensitive fields |
| 4 | Unrestricted Resource Consumption | No rate limiting, DoS attacks |
| 5 | Broken Function Level Auth | Admin endpoints accessible to regular users |
| 6 | Unrestricted Access to Sensitive Flows | Abusing business logic (coupons, voting) |
| 7 | Server Side Request Forgery | API fetches attacker-controlled URLs |
| 8 | Security Misconfiguration | Debug mode, default credentials, CORS |
| 9 | Improper Inventory Management | Deprecated/shadow APIs still accessible |
| 10 | Unsafe API Consumption | Trusting data from third-party APIs |
Authentication & Authorization
Use OAuth 2.0 + JWT
// Node.js - JWT Verification Middleware
const jwt = require('jsonwebtoken');
function authMiddleware(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
// Check token expiration
if (user.exp < Date.now() / 1000) {
return res.status(403).json({ error: 'Token expired' });
}
req.user = user;
next();
});
}
// Apply to protected routes
app.get('/api/user/profile', authMiddleware, (req, res) => {
res.json(req.user);
});
Best Practices
- Use short-lived access tokens (15-60 minutes)
- Implement refresh token rotation
- Store secrets in environment variables, not code
- Use RS256 (asymmetric) over HS256 for public APIs
Object-Level Authorization (BOLA Prevention)
// Python Flask - Prevent BOLA
@app.route('/api/orders/<order_id>')
@login_required
def get_order(order_id):
order = Order.query.get(order_id)
if not order:
return jsonify({"error": "Not found"}), 404
# CRITICAL: Verify ownership before returning data
if order.user_id != current_user.id:
# Log potential attack
log_security_event("BOLA attempt", current_user.id, order_id)
return jsonify({"error": "Forbidden"}), 403
return jsonify(order.to_dict())
Input Validation
Never trust user input. Validate everything on the server side.
// Express.js with express-validator
const { body, validationResult } = require('express-validator');
app.post('/api/users',
// Validation rules
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).matches(/[A-Z]/).matches(/[0-9]/),
body('username').isAlphanumeric().trim().escape(),
body('age').isInt({ min: 13, max: 120 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Safe to process
createUser(req.body);
}
);
SQL Injection Prevention
// BAD - SQL Injection vulnerable
db.query(`SELECT * FROM users WHERE id = ${userId}`);
// GOOD - Parameterized query
db.query('SELECT * FROM users WHERE id = ?', [userId]);
Rate Limiting
Protect against abuse, brute force, and DoS attacks.
// Express.js Rate Limiting
const rateLimit = require('express-rate-limit');
// General API limiter
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: { error: 'Too many requests, please try again later' },
standardHeaders: true,
legacyHeaders: false
});
// Stricter limiter for auth endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 login attempts per hour
message: { error: 'Account locked. Try again in 1 hour.' }
});
app.use('/api/', apiLimiter);
app.use('/api/auth/login', authLimiter);
Encryption & HTTPS
- Always use HTTPS - Redirect HTTP to HTTPS
- TLS 1.3 - Disable older protocols
- HSTS Header - Force browsers to use HTTPS
- Certificate Pinning - For mobile apps
# Nginx - Security Headers
server {
listen 443 ssl http2;
# TLS Configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Security Headers
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
}
Logging & Monitoring
You can't protect what you can't see. Log security-relevant events.
// Structured Security Logging
function logSecurityEvent(event, userId, details) {
const log = {
timestamp: new Date().toISOString(),
event: event,
userId: userId,
ip: req.ip,
userAgent: req.headers['user-agent'],
details: details,
severity: getSeverity(event)
};
// Send to SIEM (Splunk, ELK, etc.)
securityLogger.info(JSON.stringify(log));
// Alert on critical events
if (log.severity === 'CRITICAL') {
alertSecurityTeam(log);
}
}
// Events to log
logSecurityEvent('LOGIN_SUCCESS', userId);
logSecurityEvent('LOGIN_FAILED', attemptedUser);
logSecurityEvent('PERMISSION_DENIED', userId, resource);
logSecurityEvent('RATE_LIMIT_EXCEEDED', userId);
logSecurityEvent('SUSPICIOUS_PAYLOAD', userId, payload);
Complete Secure API Example
// Complete Express.js Secure API Setup
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const app = express();
// Security Middleware
app.use(helmet()); // Sets security headers
app.use(cors({
origin: ['https://myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}));
app.use(express.json({ limit: '10kb' })); // Limit body size
// Rate Limiting
app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }));
// Request Logging
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.path}`);
next();
});
// Health Check (no auth required)
app.get('/health', (req, res) => res.json({ status: 'ok' }));
// Protected Routes
app.use('/api', authMiddleware);
app.get('/api/data', (req, res) => { /* */ });
// Error Handler - Don't leak stack traces
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(3000);
Further Reading
- OWASP API Security Project:
owasp.org/API-Security - JWT Best Practices: RFC 8725
- OAuth 2.0 Security: RFC 6819
Security Checklist
- ✅ Use HTTPS everywhere
- ✅ Implement OAuth 2.0 + JWT
- ✅ Validate all input server-side
- ✅ Use parameterized queries (prevent SQLi)
- ✅ Apply rate limiting
- ✅ Check object-level authorization
- ✅ Log security events
- ✅ Set security headers
- ✅ Keep dependencies updated
- ✅ Regular penetration testing
Updated: December 2024