Skip to main content

Command Palette

Search for a command to run...

Helmet.js: Secure Express Apps

Learn: Helmet.js: Secure Express Apps

Updated
4 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Helmet.js: Secure Express Apps - HTTP Headers Security

Problem

Express applications are vulnerable to various HTTP header-based attacks by default. Without proper security headers, applications expose themselves to:

  • XSS (Cross-Site Scripting): Attackers inject malicious scripts into web pages
  • Clickjacking: Malicious sites frame your application to trick users
  • MIME-type sniffing: Browsers misinterpret file types, executing harmful content
  • Man-in-the-Middle attacks: Unencrypted connections allow interception
  • Information disclosure: Server details leak through headers
  • Insecure dependencies: Vulnerable libraries compromise security

These vulnerabilities exist because Express doesn't set security headers by default, leaving applications exposed to common web attacks.

Solution

Helmet.js is a comprehensive middleware suite that automatically sets 15+ HTTP security headers, following OWASP best practices. It provides:

  • Content Security Policy (CSP): Controls resource loading
  • X-Frame-Options: Prevents clickjacking
  • X-Content-Type-Options: Prevents MIME sniffing
  • Strict-Transport-Security (HSTS): Enforces HTTPS
  • X-XSS-Protection: Legacy XSS protection
  • Referrer-Policy: Controls referrer information
  • Permissions-Policy: Restricts browser features
  • Dependency vulnerability scanning: Identifies unsafe packages

Helmet.js provides defense-in-depth security with minimal configuration overhead.

Code Implementation

Basic Setup

// app.js
const express = require('express');
const helmet = require('helmet');

const app = express();

// Apply Helmet middleware - sets all default security headers
app.use(helmet());

app.get('/', (req, res) => {
  res.send('Secure Express App');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Advanced Configuration

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

const app = express();

// Custom Helmet configuration
app.use(helmet({
  // Content Security Policy
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'", 'trusted-cdn.com'],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'api.example.com'],
      fontSrc: ["'self'", 'fonts.googleapis.com'],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },

  // Clickjacking protection
  frameguard: {
    action: 'deny', // or 'sameorigin'
  },

  // MIME-type sniffing prevention
  noSniff: true,

  // XSS protection (legacy)
  xssFilter: true,

  // HTTPS enforcement
  hsts: {
    maxAge: 31536000, // 1 year in seconds
    includeSubDomains: true,
    preload: true,
  },

  // Referrer policy
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin',
  },

  // Permissions policy (formerly Feature-Policy)
  permissionsPolicy: {
    features: {
      geolocation: ["'none'"],
      microphone: ["'none'"],
      camera: ["'none'"],
      payment: ["'self'"],
    },
  },

  // Remove X-Powered-By header
  hidePoweredBy: true,
}));

// Additional security middleware
app.use(express.json({ limit: '10kb' })); // Limit payload size
app.use(express.urlencoded({ limit: '10kb', extended: true }));

// CORS configuration
const cors = require('cors');
app.use(cors({
  origin: ['https://trusted-domain.com'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

// Rate limiting
const rateLimit = require('express-rate-limit');
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);

// Routes
app.get('/', (req, res) => {
  res.json({ message: 'Secure API' });
});

app.post('/api/data', (req, res) => {
  // Validate and sanitize input
  const { data } = req.body;
  res.json({ received: data });
});

// Error handling
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ 
    error: 'Internal Server Error',
    // Don't expose stack traces in production
  });
});

app.listen(3000, () => {
  console.log('Secure server running on port 3000');
});

Selective Header Configuration

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

const app = express();

// Disable specific headers if needed
app.use(helmet({
  contentSecurityPolicy: false, // Disable CSP
  frameguard: true,
  hsts: true,
}));

// Or apply individual middleware
app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.hsts({ maxAge: 31536000 }));
app.use(helmet.noSniff());
app.use(helmet.xssFilter());
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));

app.listen(3000);

Production-Ready Setup

const express = require('express');
const helmet = require('helmet');
const mongoSanitize = require('express-mongo-sanitize');
const rateLimit = require('express-rate-limit');
const cors = require('cors');

const app = express();

// Trust proxy
app.set('trust proxy', 1);

// Security headers
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
}));

// CORS
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(','),
  credentials: true,
}));

// Body parser with size limits
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ limit: '10kb', extended: true }));

// Data sanitization against NoSQL injection
app.use(mongoSanitize());

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
});
app.use('/api/', limiter);

// Routes
app.get('/health', (req, res) => {
  res.json({ status: 'OK' });
});

// 404 handler
app.use((req, res) => {
  res.status(404).json({ error: 'Not Found' });
});

// Error handler
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    error: process.env.NODE_ENV === 'production' 
      ? 'Internal Server Error' 
      : err.message,
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Security Headers Explained

HeaderPurposeDefault Value
Content-Security-PolicyControls resource loadingRestrictive
X-Frame-OptionsPrevents clickjackingDENY
X-Content-Type-OptionsPrevents MIME sniffingnosniff
Strict-Transport-SecurityEnforces HTTPS180 days
X-XSS-ProtectionLegacy XSS protection1; mode=block
Referrer-PolicyControls referrer infono-referrer
Permissions-PolicyRestricts browser featuresRestrictive

Installation

npm install helmet
npm install express-rate-limit express-mongo-sanitize cors

Key Takeaways

Always use Helmet.js in production Express applications
Customize CSP based on your application's resource needs
Enable HSTS preload for maximum HTTPS enforcement
Combine with rate limiting and input validation
Test headers using security scanning tools
Keep dependencies updated for latest security patches

Helmet.js provides essential HTTP header security with minimal configuration, making it a must-have for any Express application handling sensitive data.