Express.js Security Best Practices

Secure your Express.js application against common vulnerabilities. Learn about CORS, helmet, input validation, and security headers.

Overview

Security is paramount in web applications. Express.js apps need protection against common vulnerabilities like XSS, CSRF, and injection attacks.

Common Vulnerabilities

- **XSS (Cross-Site Scripting)**: Injecting malicious scripts - **CSRF (Cross-Site Request Forgery)**: Unauthorized commands from trusted users - **SQL/NoSQL Injection**: Malicious database queries - **Directory Traversal**: Accessing unauthorized files

Security Middleware

Use packages like Helmet to set secure HTTP headers. It helps protect against common attacks with minimal configuration.

Input Validation

Never trust user input. Validate and sanitize all data before processing.

HTTPS and CORS

Always use HTTPS in production. Configure CORS properly to control which origins can access your API.

Code Examples

Helmet Security Headers

middleware/security.js
const helmet = require('helmet');
const cors = require('cors');

// Apply security headers
app.use(helmet());

// Configure CORS
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400 // 24 hours
}));

// Prevent parameter pollution
const hpp = require('hpp');
app.use(hpp());

// Limit request body size
app.use(express.json({ limit: '10kb' }));

Input Validation with Joi

validation/userValidation.js
const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required(),
  password: Joi.string()
    .min(8)
    .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
    .required()
    .messages({
      'string.pattern.base': 'Password must contain uppercase, lowercase, and number'
    })
});

const validate = (schema) => {
  return (req, res, next) => {
    const { error, value } = schema.validate(req.body, { abortEarly: false });

    if (error) {
      return res.status(400).json({
        status: 'fail',
        errors: error.details.map(d => d.message)
      });
    }

    req.body = value; // Use sanitized value
    next();
  };
};

app.post('/register', validate(userSchema), registerController);

Sanitize MongoDB Queries

middleware/sanitize.js
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');

// Prevent NoSQL injection
app.use(mongoSanitize());

// Prevent XSS attacks
app.use(xss());

// Custom sanitization for specific fields
const sanitizeInput = (req, res, next) => {
  if (req.body) {
    // Remove any $ or . from object keys
    const sanitize = (obj) => {
      for (let key in obj) {
        if (key.startsWith('$') || key.includes('.')) {
          delete obj[key];
        } else if (typeof obj[key] === 'object') {
          sanitize(obj[key]);
        }
      }
    };
    sanitize(req.body);
  }
  next();
};

app.use(sanitizeInput);

Frequently Asked Questions

Is helmet enough for security?
Helmet is a great start but not complete security. You also need input validation, authentication, rate limiting, HTTPS, secure cookie settings, and proper error handling. Security is layered.
How do I protect against CSRF attacks?
Use CSRF tokens for form submissions (csurf package), implement SameSite cookie attributes, verify Origin/Referer headers for API requests, and use proper CORS configuration.
Should I store sensitive data in environment variables?
Yes, never hardcode secrets. Use environment variables and a .env file for local development. In production, use your platform's secret management. Never commit .env files to version control.

Need expert help with Express.js?

Our team at Slashdev.io builds production-ready Express.js applications.