Express Security — Complete Guide to Securing Express Applications
In this tutorial, you will learn about Express Security. We cover key concepts, practical examples, and best practices to help you master this topic.
Express security involves multiple layers: HTTP headers, input validation, authentication, Rate Limiting, and dependency management to protect against common web vulnerabilities.
What You'll Learn
By the end of this tutorial, you'll configure Helmet, CORS, rate limiting, input sanitization, prevent SQL Injection and XSS, implement CSRF protection, and secure your Express app for production.
Why Express Security Matters
Web applications face constant attacks. A single vulnerability (XSS, CSRF, injection) can compromise user data, damage reputation, and lead to legal liability. Security must be built in, not bolted on.
Real-World Use
A fintech API implements Helmet for security headers, rate limiting to prevent brute force, input validation to block injection, and CORS to restrict API access to authorized domains.
Express Security Learning Path
flowchart LR
A[Sessions] --> B[Security]
B --> C[REST API]
C --> D[GraphQL]
D --> E[Authentication]
A --> F{You Are Here}
style F fill:#f90,color:#fff
Helmet — Security Headers
npm install helmet
import helmet from "helmet";
app.use(helmet()); // Sets 15+ security headers
// Individual configuration:
app.use(helmet({
contentSecurityPolicy: {
directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "example.com"] }
},
referrerPolicy: { policy: "same-origin" }
}));
CORS Configuration
npm install cors
import cors from "cors";
// Allow all (development only)
app.use(cors());
// Restrict in production
app.use(cors({
origin: ["https://myapp.com", "https://admin.myapp.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
maxAge: 86400
}));
Rate Limiting
npm install express-rate-limit
import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
message: { error: "Too many requests, please try again later" }
});
app.use("/api", limiter);
// Stricter for auth endpoints
const authLimiter = rateLimit({ windowMs: 60 * 1000, max: 5 });
app.use("/api/login", authLimiter);
Input Validation
npm install express-validator
import { body, validationResult } from "express-validator";
app.post("/users",
body("email").isEmail().normalizeEmail(),
body("age").isInt({ min: 0, max: 150 }),
body("name").trim().isLength({ min: 2, max: 50 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
res.json({ success: true });
}
);
CSRF Protection
npm install csurf
import csurf from "csurf";
const csrfProtection = csurf({ cookie: true });
app.use(csrfProtection);
app.get("/form", (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
SQL Injection Prevention
// WRONG (string interpolation)
const query = `SELECT * FROM users WHERE id = ${userId}`;
// RIGHT (parameterized query)
const query = "SELECT * FROM users WHERE id = $1";
db.query(query, [userId]);
Common Mistakes
1. Trusting User Input
Never trust req.body, req.query, or req.params. Always validate, sanitize, and escape user input.
2. Exposing Stack Traces in Production
Error middleware should return generic messages in production. Stack traces reveal application internals.
3. Using Deprecated or Unmaintained Packages
Old packages may have known vulnerabilities. Run npm audit regularly and update dependencies.
4. Storing Secrets in Code
Never hardcode API keys, database passwords, or session secrets. Use environment variables.
5. Disabling CSRF for Convenience
CSRF tokens add complexity but are essential for cookie-based auth apps. Don't skip them.
Practice Questions
1. What security headers does Helmet set?
Helmet sets Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, X-XSS-Protection, and others.
2. Why is CORS needed?
Browsers block cross-origin requests by default. CORS headers tell the browser which origins are allowed to access the API.
3. How does rate limiting prevent attacks?
It limits requests per time window, preventing brute force attacks, DDoS, and API abuse.
4. What is the difference between input validation and sanitization?
Validation rejects invalid input. Sanitization cleans valid-but-dangerous input (removing script tags from user content).
5. Challenge: Set up a secure Express server with Helmet, CORS, rate limiting, and input validation.
import express from "express";
import helmet from "helmet";
import cors from "cors";
import rateLimit from "express-rate-limit";
const app = express();
app.use(helmet());
app.use(cors({ origin: "https://myapp.com" }));
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
app.use(express.json({ limit: "10kb" }));
app.listen(3000);
FAQ
Mini Project: Secure Express Boilerplate
Create a secure Express application with layered security.
import express from "express";
import helmet from "helmet";
import cors from "cors";
import rateLimit from "express-rate-limit";
import { body, validationResult } from "express-validator";
const app = express();
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));
app.use(rateLimit({ windowMs: 60000, max: 60 }));
app.use(express.json({ limit: "1mb" }));
app.post("/api/contact", [
body("email").isEmail().normalizeEmail(),
body("message").trim().isLength({ min: 10 })
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
res.json({ success: true });
});
app.listen(3000);
What's Next
REST API Express GraphQL Express Node.js Authentication
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro