Helmet.js: Secure Express Apps
Learn: Helmet.js: Secure Express Apps
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
| Header | Purpose | Default Value |
| Content-Security-Policy | Controls resource loading | Restrictive |
| X-Frame-Options | Prevents clickjacking | DENY |
| X-Content-Type-Options | Prevents MIME sniffing | nosniff |
| Strict-Transport-Security | Enforces HTTPS | 180 days |
| X-XSS-Protection | Legacy XSS protection | 1; mode=block |
| Referrer-Policy | Controls referrer info | no-referrer |
| Permissions-Policy | Restricts browser features | Restrictive |
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.