DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Securing Node.js APIs: Helmet, CORS, and CSRF Protection

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
Enter fullscreen mode Exit fullscreen mode
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'],
    },
  },
}));
Enter fullscreen mode Exit fullscreen mode

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'],
}));
Enter fullscreen mode Exit fullscreen mode

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),
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

Security hardening — Helmet, CORS, rate limiting, input validation — is production-configured in the AI SaaS Starter Kit.

Top comments (0)