Skip to content

Express Security Overview

Express is unopinionated about security by default, most protections are opt-in middleware you choose to add. This lesson gives an overview of the essential layers, each covered in more depth in the following lessons.

The Layers of Express Security

Securing an Express app is rarely one single fix, it's a combination of layers: secure HTTP headers, controlled cross-origin access, rate limiting against abuse, strict input validation, safe secret management, and careful dependency hygiene.

None of these individually make an app "secure", but skipping any one of them leaves a real, well-documented gap that attackers actively look for.

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: process.env.ALLOWED_ORIGIN }));
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
app.use(express.json({ limit: '100kb' }));

This is a realistic security baseline for a production Express API, headers, CORS, rate limiting, and a sane body size limit, before a single route is even defined.

A Security Checklist

[ ] helmet() for secure HTTP headers
[ ] cors() configured with an explicit allowlist
[ ] express-rate-limit on public/auth endpoints
[ ] Input validation on every route accepting data
[ ] Secrets in environment variables, never in source
[ ] HTTPS enforced in production
[ ] Dependencies kept up to date (npm audit)
  • Security headers (Helmet) protect against common browser-based attacks like clickjacking and MIME sniffing.
  • CORS controls which origins are allowed to call your API from a browser.
  • Rate limiting protects against brute-force and denial-of-service style abuse.
  • Regularly run npm audit to catch known vulnerabilities in dependencies.

Express Security Cheatsheet

The main security concerns and the tools that address them.

Concern Tool / Practice
Insecure headers helmet()
Unrestricted cross-origin access cors() with an explicit origin allowlist
Brute-force / abuse express-rate-limit
Invalid/malicious input express-validator, Joi, or Zod
Leaked secrets .env + dotenv, never hardcoded
SQL/NoSQL injection Parameterized queries, schema validation
Outdated dependencies npm audit, Dependabot

Enforcing HTTPS in Production

Without HTTPS, all traffic, including cookies, tokens, and passwords, travels in plain text and can be intercepted. Most hosting platforms (Heroku, Render, Vercel) terminate TLS for you; when you manage your own server, redirect HTTP to HTTPS explicitly.

app.use((req, res, next) => {
  if (req.secure || req.headers['x-forwarded-proto'] === 'https') {
    return next();
  }
  res.redirect(`https://${req.headers.host}${req.url}`);
});

Dependency Hygiene

Every npm package you install is code you're trusting to run inside your server. Keep dependencies updated, remove unused ones, and run npm audit regularly to catch known vulnerabilities before they become a problem.

npm audit
npm audit fix
npm outdated

Common Mistakes

  • Treating security as a single checkbox instead of multiple independent layers.
  • Allowing CORS from any origin (origin: '*') on an API that handles authenticated requests.
  • Deploying without HTTPS, or without redirecting HTTP to HTTPS.
  • Never running npm audit, letting known vulnerabilities accumulate silently.

Key Takeaways

  • Express security is a combination of independent layers, not a single setting.
  • Helmet, CORS configuration, and rate limiting form a solid baseline for any production API.
  • Input validation and parameterized queries prevent injection-based attacks.
  • Regular dependency audits catch known vulnerabilities before they're exploited.

Pro Tip

Add helmet(), a scoped cors() config, and express-rate-limit to a brand-new Express project on day one, retrofitting security middleware into a live production app is far riskier than starting with it in place.