
REST API Security Best Practices: Token Rotation, Rate Limiting, and Headers
Exposing raw database records and business logic through public REST APIs is a fundamental part of web development. However, once an API is accessible on the open internet, it becomes a target for automated scrapers, brute-force exploits, and Distributed Denial of Service (DDoS) attacks.
API security requires a defensive approach. You cannot rely on a single firewall or simple authentication checks. You must secure authentication pipelines, limit access speeds, isolate cross-origin domains, and configure browser security response headers.
In this guide, we will analyze essential security practices to harden your production REST APIs.
1. Rate Limiting with Redis
Rate limiting restricts how many API calls a user (or IP address) can make within a specified time window. This prevents scrapers from downloading your database and protects servers from getting overwhelmed by automated traffic.
A standard pattern is storing client request rates inside Redis using a Sliding Window Counter or Token Bucket algorithm.
Here is a basic Node.js rate-limiting middleware using Redis:
import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';
const redis = new Redis();
const LIMIT = 100; // Max 100 requests
const WINDOW_SIZE = 60; // 60 seconds window
export async function rateLimiter(req: Request, res: Response, next: NextFunction) {
// Use IP address as the identifier
const ip = req.ip || req.headers['x-forwarded-for'] || 'unknown';
const key = `ratelimit:${ip}`;
try {
const currentRequests = await redis.incr(key);
if (currentRequests === 1) {
// Set expiration window on first request
await redis.expire(key, WINDOW_SIZE);
}
if (currentRequests > LIMIT) {
// Reject request with 429 Too Many Requests
return res.status(429).json({
error: 'Too many requests. Please try again later.',
});
}
next();
} catch (err) {
// Fail-safe: log error but do not block user if Redis fails
console.error('Rate limiter failed:', err);
next();
}
}2. Hardening HTTP Security Headers
Every API response should include standard HTTP security headers to inform client browsers how to handle scripts and requests safely.
Configure these headers inside your API Gateway or application middleware:
- Strict-Transport-Security (HSTS): Enforces browsers to connect strictly via HTTPS, blocking man-in-the-middle decryption attempts:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload- X-Content-Type-Options: Prevents browsers from guessing the MIME type of a file, blocking execution of user-uploaded images containing hidden script tags:
X-Content-Type-Options: nosniff- X-Frame-Options: Blocks clickjacking exploits by preventing your pages from being embedded inside external iframes:
X-Frame-Options: DENY3. Secure CORS Configurations
Cross-Origin Resource Sharing (CORS) is a browser security mechanism that restricts websites from requesting assets from a different domain.
When configuring CORS in your API, avoid two critical security mistakes:
- Never use wildcard origins with credentials: If your API supports authentication cookies or authorization headers, setting
Access-Control-Allow-Origin: *alongsideAccess-Control-Allow-Credentials: trueis rejected by modern browsers. - Never echo the Request Origin: Avoid writing backend logic that automatically grabs the request domain and places it in the origin header. This defeats the purpose of CORS.
Instead, define an explicit whitelist of trusted domains:
import cors from 'cors';
const whitelist = ['https://example.com', 'https://admin.example.com'];
const corsOptions = {
origin: (origin: string | undefined, callback: any) => {
if (!origin || whitelist.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Blocked by CORS policy'));
}
},
credentials: true,
};
app.use(cors(corsOptions));4. Input Payload Validation and Schema Enforcement
Never trust client input. Every payload sent to your POST, PUT, or PATCH endpoints must be validated against a strict schema before processing it in database queries or application logic.
Use libraries like Zod to validate structures, strip unexpected fields (preventing parameter pollution), and cast values to correct data types automatically.
Conclusion
Securing a REST API is a continuous engineering discipline. By implementing sliding window rate limiting with Redis to prevent scraping abuse, configuring standard HTTP security headers to lock down client rendering, defining explicit whitelisted CORS domains, and validating all input payloads using strict schema parsers, you protect your system from common API vulnerabilities.