Securing Node.js APIs: Helmet, CORS, and CSRF Protection
A checklist of security headers and middleware every production Node.js API needs.
Helmet: Security Headers in One Line
npm install helmet
import helmet from 'helmet';
app.use(helmet()); // Adds 11 security headers automatically
// What helmet sets:
// X-DNS-Prefetch-Control: off
// X-Frame-Options: SAMEORIGIN (prevents clickjacking)
// X-Content-Type-Options: nosniff (prevents MIME sniffing)
// Strict-Transport-Security (forces HTTPS)
// X-XSS-Protection: 0 (modern browsers don't need it)
// Content-Security-Policy (restricts resource loading)
// ...and more
// Customize CSP if needed
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'https://js.stripe.com'],
frameSrc: ['https://js.stripe.com'],
},
},
}));
CORS: Allowing the Right Origins
import cors from 'cors';
const allowedOrigins = [
'https://yourapp.com',
'https://www.yourapp.com',
...(process.env.NODE_ENV === 'development' ? ['http://localhost:3000'] : []),
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true, // Allow cookies
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
CSRF Protection
// For session-based auth: use CSRF tokens
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
// Get token endpoint
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// Protect state-changing routes
app.post('/api/orders', csrfProtection, createOrder);
// Client: include CSRF token in requests
const { csrfToken } = await fetch('/api/csrf-token').then(r => r.json());
await fetch('/api/orders', {
method: 'POST',
headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json' },
body: JSON.stringify(order),
});
Note: If you use JWT auth (Authorization: Bearer), you're already CSRF-safe — CSRF attacks can't set custom headers.
Rate Limiting
import rateLimit from 'express-rate-limit';
// Strict limit on auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10, // 10 attempts per 15 min
message: 'Too many attempts, try again later',
});
app.use('/api/auth', authLimiter);
Input Validation
// Always validate at the API boundary
import { z } from 'zod';
app.post('/api/users', async (req, res) => {
const result = z.object({
email: z.string().email(),
password: z.string().min(8).max(100),
}).safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: result.error.flatten() });
}
// Safe to use result.data
});
Security hardening — Helmet, CORS, rate limiting, input validation — is production-configured in the AI SaaS Starter Kit.
Top comments (0)