Skip to content

Node.js Helmet, CORS, and Rate Limiting — Complete Guide to Express Security

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Node.js Helmet, CORS, and Rate Limiting. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js Helmet, CORS, and rate limiting form the essential security middleware triad for Express applications, protecting against XSS, clickjacking, unauthorized origins, and brute-force attacks.

What You'll Learn

By the end of this tutorial, you'll configure Helmet headers, implement CORS policies, set up rate limiting with express-rate-limit, combine them effectively, and handle edge cases.

Why This Triad Matters

These three middleware layers block the most common attack vectors: Helmet prevents browser-based attacks, CORS controls cross-origin access, and rate limiting throttles abusive traffic.

Real-World Use

An Express API serves a React frontend on a different domain. Helmet prevents XSS, CORS allows only the React domain, and rate limiting blocks brute-force login attempts at 5 tries per minute.

Security Middleware Path

flowchart LR
  A[Security Checklist] --> B[Helmet/CORS/RateLimit]
  B --> C[SSRF Protection]
  C --> D[JWT/OAuth]
  D --> E[Deployment]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Helmet Deep Configuration

Customize Helmet to set appropriate security headers for your application.

const helmet = require("helmet");
const express = require("express");
const app = express();
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.example.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:"],
      connectSrc: ["'self'", "https://api.example.com"],
    },
  },
  crossOriginEmbedderPolicy: false,
  crossOriginResourcePolicy: { policy: "cross-origin" },
}));

CORS Advanced Configuration

Handle complex CORS scenarios like credentials, preflight Caching, and dynamic origins.

const cors = require("cors");
const express = require("express");
const app = express();
const whitelist = ["https://app.example.com", "https://admin.example.com"];
const corsOptions = {
  origin: (origin, callback) => {
    if (process.env.NODE_ENV === "development" || !origin) {
      return callback(null, true);
    }
    if (whitelist.includes(origin)) {
      return callback(null, true);
    }
    callback(new Error("Origin not allowed"));
  },
  methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
  exposedHeaders: ["X-RateLimit-Remaining"],
  credentials: true,
  maxAge: 86400,
};
app.use(cors(corsOptions));

Rate Limiting Strategies

Different endpoints need different rate limits. Use multiple limiters for granular control.

const rateLimit = require("express-rate-limit");
const express = require("express");
const app = express();
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 200,
  message: "Too many requests from this IP",
});
const authLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
  message: "Too many login attempts. Try again later.",
});
const apiLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 60,
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.headers["x-api-key"] || req.ip,
});
app.use(globalLimiter);
app.use("/auth/login", authLimiter);
app.use("/api/", apiLimiter);

Combining All Three

Apply the triad in the correct order for maximum effectiveness.

const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const rateLimit = require("express-rate-limit");
const app = express();
app.use(helmet());
app.use(cors({ origin: "https://app.example.com", credentials: true }));
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
app.get("/api/data", (req, res) => {
  res.json({ secured: true });
});
app.listen(3000);

Handling CORS Preflight

CORS preflight OPTIONS requests must be handled before other middleware.

const cors = require("cors");
const express = require("express");
const app = express();
app.options("*", cors()); // Handle preflight for all routes
app.use(cors({ origin: "https://app.example.com" }));
app.get("/api/data", (req, res) => {
  res.json({ message: "CORS enabled" });
});

Common Mistakes

1. CSP Blocking Legitimate Resources

Overly strict Content-Security-Policy breaks scripts and styles. Test in report-only mode first.

2. CORS Returning Wildcard with Credentials

Access-Control-Allow-Origin: * and credentials: true cannot be combined. Specify explicit origins.

3. Not Differentiating Rate Limits by Endpoint

Same rate limit for login and data endpoints allows brute-force on login. Use separate limiters.

4. Forgetting CORS Preflight Cache

Preflight requests add latency. Set Access-Control-Max-Age to cache preflight responses (up to 24h).

5. Disabling Helmet for CSP Report-Only

Use reportOnly: true to test CSP without blocking, then switch to enforce mode after testing.

Practice Questions

1. What is the purpose of Content-Security-Policy header?

Restricts which resources (scripts, styles, images) the browser can load, preventing XSS and data injection.

2. How does CORS prevent unauthorized cross-origin requests?

The browser blocks requests that do not include the correct Access-Control-Allow-Origin header from the server.

3. What is a CORS preflight request?

An OPTIONS request sent by browsers before actual cross-origin requests with non-simple methods or headers.

4. How do you implement per-IP rate limiting with express-rate-limit?

It uses req.ip by default. Override keyGenerator for custom keys like API keys or user IDs.

5. Challenge: Create a security middleware that logs all CORS violations.

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (origin && !allowedOrigins.includes(origin)) {
    console.warn(`CORS violation from ${origin} to ${req.method} ${req.path}`);
  }
  next();
});

FAQ

What headers does Helmet set by default?

X-DNS-Prefetch-Control, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Strict-Transport-Security, and CSP.

Can I use Helmet without Express?

Helmet is Express middleware. For other frameworks, set headers manually using res.setHeader().

What is the default rate limit window in express-rate-limit?

Default is 1 minute with max 5 requests. Always configure for your use case.

How do I handle CORS errors on the client?

Check that the server sends the correct Access-Control-Allow-Origin matching the client origin.

Should I use a CDN for Helmet headers?

CDNs like Cloudflare can set security headers at the edge. Combine with Helmet for origin server headers.

Mini Project: Security Middleware Factory

Build a reusable factory that generates Helmetted, CORS-configured, rate-limited Express apps.

const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const rateLimit = require("express-rate-limit");
function createSecureApp(config = {}) {
  const app = express();
  app.use(helmet(config.helmet));
  app.use(cors(config.cors || { origin: "https://app.example.com" }));
  app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: config.rateLimit || 100 }));
  return app;
}
const app = createSecureApp({ rateLimit: 200 });
app.get("/", (req, res) => res.json({ status: "secure" }));
module.exports = app;

What's Next

Node.js SSRF Protection Node.js Security Checklist Node.js JWT Authentication

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro