Skip to main content
Security is critical for production applications. This guide covers essential security practices for Express apps.

Use Helmet

Helmet helps secure Express apps by setting various HTTP headers.
1

Install Helmet

npm install helmet
2

Use Helmet middleware

const helmet = require('helmet');
const express = require('express');
const app = express();

app.use(helmet());
Helmet sets multiple security headers including Content-Security-Policy, X-DNS-Prefetch-Control, X-Frame-Options, and more.

Prevent SQL Injection

Always use parameterized queries or prepared statements:
const userId = req.params.id;
const query = 'SELECT * FROM users WHERE id = ' + userId; // DON'T DO THIS!
db.query(query);
Never concatenate user input directly into SQL queries. Always use parameterized queries.

Prevent Cross-Site Scripting (XSS)

1

Escape output in templates

Always escape user-generated content:
<!-- EJS - Use <%= not <%- -->
<p>Welcome <%= username %></p>

<!-- Pug - Automatic escaping -->
p Welcome #{username}
2

Sanitize input

npm install express-validator
const { body, validationResult } = require('express-validator');

app.post('/user',
  body('email').isEmail().normalizeEmail(),
  body('name').trim().escape(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Process valid data
  }
);
3

Set Content-Security-Policy

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "trusted-cdn.com"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", "data:", "https:"]
  }
}));

Use HTTPS

Always use HTTPS in production to encrypt data in transit.
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();

const options = {
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem')
};

https.createServer(options, app).listen(443);
For production, use a reverse proxy (nginx, Apache) or service (Cloudflare, AWS) to handle HTTPS.

Secure Session Management

const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');

const redisClient = createClient();
redisClient.connect().catch(console.error);

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,      // Require HTTPS
    httpOnly: true,    // Prevent client-side access
    maxAge: 3600000,   // 1 hour
    sameSite: 'strict' // CSRF protection
  }
}));
Use a strong, random session secret and store it in environment variables. Never commit secrets to version control.

Rate Limiting

Prevent brute-force attacks with rate limiting:
npm install express-rate-limit
const rateLimit = require('express-rate-limit');

// General rate limiter
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP'
});

app.use('/api/', limiter);

// Stricter rate limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: 'Too many login attempts'
});

app.post('/login', authLimiter, (req, res) => {
  // Handle login
});

Prevent NoSQL Injection

Sanitize MongoDB queries:
npm install express-mongo-sanitize
const mongoSanitize = require('express-mongo-sanitize');

app.use(mongoSanitize());

CSRF Protection

Protect against Cross-Site Request Forgery:
npm install csurf
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.use(csrfProtection);

app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/submit', (req, res) => {
  // CSRF token automatically validated
  res.send('Success');
});
<form method="POST" action="/submit">
  <input type="hidden" name="_csrf" value="{{csrfToken}}">
  <button type="submit">Submit</button>
</form>

Secure Password Storage

Never store passwords in plain text:
npm install bcrypt
const bcrypt = require('bcrypt');
const saltRounds = 10;

// Hash password
app.post('/register', async (req, res) => {
  const hashedPassword = await bcrypt.hash(req.body.password, saltRounds);
  // Store hashedPassword in database
});

// Verify password
app.post('/login', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });
  const match = await bcrypt.compare(req.body.password, user.password);
  
  if (match) {
    // Password correct
  } else {
    // Password incorrect
  }
});

Environment Variables

Never commit secrets or API keys to version control. Use environment variables.
npm install dotenv
require('dotenv').config();

const dbPassword = process.env.DB_PASSWORD;
const apiKey = process.env.API_KEY;
const sessionSecret = process.env.SESSION_SECRET;
DB_PASSWORD=your_secure_password
API_KEY=your_api_key
SESSION_SECRET=your_session_secret
NODE_ENV=production

Input Validation

Validate all user input:
const { body, param, validationResult } = require('express-validator');

app.post('/user/:id',
  param('id').isInt(),
  body('email').isEmail().normalizeEmail(),
  body('age').optional().isInt({ min: 0, max: 120 }),
  body('username').trim().isLength({ min: 3, max: 30 }).isAlphanumeric(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Process validated data
  }
);

Dependency Security

1

Audit dependencies regularly

npm audit
npm audit fix
2

Keep dependencies updated

npm outdated
npm update
3

Use automated tools

npm install -g snyk
snyk test

Additional Security Headers

const express = require('express');
const app = express();

// Disable X-Powered-By header
app.disable('x-powered-by');

// Or use Helmet to set multiple headers
const helmet = require('helmet');
app.use(helmet());

Security Checklist

  • Use HTTPS in production
  • Use Helmet for security headers
  • Validate and sanitize all input
  • Use parameterized queries
  • Hash passwords with bcrypt
  • Implement rate limiting
  • Use CSRF protection for forms
  • Set secure session cookies
  • Keep dependencies updated
  • Use environment variables for secrets
  • Implement Content Security Policy
  • Use SameSite cookies
  • Implement proper authentication
  • Add request size limits
  • Implement proper error handling
  • Log security events
  • Use security linters (eslint-plugin-security)
  • Implement account lockout after failed attempts

Build docs developers (and LLMs) love