Express.js is minimalist - you must add security features yourself. This checklist ensures your Node.js/Express app is production-ready on Heroku.
Expedited WAF for Express
Expedited WAF adds network level request filtering, preventing malicious attacks from ever touching your application. This enables true defense in depth for your Express app—even if your framework has a vulnerability or is improperly configured, Expedited WAF dynamically blocks threats before they reach your code.
From experience, we’ve seen many applications where attacks technically didn’t succeed, but burned through so many server resources that the application went down anyway. By filtering malicious requests at the network edge, Expedited WAF protects both your security and your availability.
New Security Features for Your Express App
These capabilities aren’t available out of the box with Express, but Expedited WAF adds them instantly:
- Block IP Addresses - Stop malicious actors by IP or CIDR range
- Block User Agents - Filter out bad bots and scrapers
- Block by Geolocation - Restrict traffic by country or region
- DDoS Protection - Stop attacks with CAPTCHA challenges
- Block Anonymous Proxies - Prevent traffic from VPNs and proxy networks
Core Security Configuration
Use Helmet for security headers
const helmet = require('helmet'); app.use(helmet()); // Or configure specific headers app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"] } }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true } }));Disable X-Powered-By header
app.disable('x-powered-by');Set secure session configuration
const session = require('express-session'); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production', // HTTPS only httpOnly: true, maxAge: 1000 * 60 * 60 * 24, // 24 hours sameSite: 'strict' } }));Enable trust proxy for Heroku
app.set('trust proxy', 1); // Trust first proxy (Heroku router)
HTTPS & SSL
Enforce HTTPS in production
if (process.env.NODE_ENV === 'production') { app.use((req, res, next) => { if (req.header('x-forwarded-proto') !== 'https') { res.redirect(`https://${req.header('host')}${req.url}`); } else { next(); } }); }Configure HSTS
- Included in Helmet
- How to Force HTTPS on Heroku
Input Validation & Sanitization
Validate all input
const { body, validationResult } = require('express-validator'); app.post('/user', [ body('email').isEmail().normalizeEmail(), body('password').isLength({ min: 12 }), body('name').trim().escape() ], (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } // Process valid data });Sanitize user input
const validator = require('validator'); const cleanInput = validator.escape(userInput);Prevent NoSQL injection
const mongoSanitize = require('express-mongo-sanitize'); app.use(mongoSanitize());Limit request body size
app.use(express.json({ limit: '10kb' })); app.use(express.urlencoded({ extended: true, limit: '10kb' }));
CSRF Protection
Implement CSRF protection
const csrf = require('csurf'); const csrfProtection = csrf({ cookie: true }); app.use(csrfProtection); app.get('/form', (req, res) => { res.render('form', { csrfToken: req.csrfToken() }); });Add CSRF token to forms
<input type="hidden" name="_csrf" value="{{csrfToken}}">
Rate Limiting & DDoS Protection
Implement 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); // Stricter limits for auth endpoints const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, skipSuccessfulRequests: true }); app.post('/login', authLimiter, loginHandler);Prevent brute force attacks
const ExpressBrute = require('express-brute'); const store = new ExpressBrute.MemoryStore(); const bruteforce = new ExpressBrute(store); app.post('/login', bruteforce.prevent, loginHandler);Configure Heroku DDoS protection
Authentication & Session Security
Use passport.js for authentication
const passport = require('passport'); const LocalStrategy = require('passport-local').Strategy; passport.use(new LocalStrategy( function(username, password, done) { // Verify credentials } )); app.use(passport.initialize()); app.use(passport.session());Hash passwords with bcrypt
const bcrypt = require('bcrypt'); const saltRounds = 12; // Hashing const hashedPassword = await bcrypt.hash(password, saltRounds); // Comparing const match = await bcrypt.compare(password, hashedPassword);Implement JWT for APIs
const jwt = require('jsonwebtoken'); // Generate token const token = jwt.sign( { userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' } ); // Verify token middleware function authenticateToken(req, res, next) { const token = req.headers['authorization']?.split(' ')[1]; if (!token) return res.sendStatus(401); jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) return res.sendStatus(403); req.user = user; next(); }); }
Database Security
Use environment variables for DB connection
const mongoose = require('mongoose'); mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, ssl: true });Use parameterized queries
// Good - parameterized User.findOne({ email: userEmail }); // Bad - injection risk db.query(`SELECT * FROM users WHERE email = '${userEmail}'`);Enable Mongoose schema validation
const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true, trim: true }, password: { type: String, required: true, minlength: 12 } });
CORS Configuration
Configure CORS properly
const cors = require('cors'); const corsOptions = { origin: process.env.ALLOWED_ORIGINS?.split(',') || 'https://yourdomain.com', credentials: true, optionsSuccessStatus: 200 }; app.use(cors(corsOptions));Never use CORS wildcard in production
// Bad - allows all origins app.use(cors()); // Good - specific origins app.use(cors({ origin: 'https://yourdomain.com' }));
File Uploads
Validate file uploads
const multer = require('multer'); const upload = multer({ limits: { fileSize: 5 * 1024 * 1024 // 5MB }, fileFilter: (req, file, cb) => { const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Invalid file type')); } } }); app.post('/upload', upload.single('avatar'), uploadHandler);Store uploads on S3, not Heroku filesystem
const aws = require('aws-sdk'); const multerS3 = require('multer-s3'); const s3 = new aws.S3({ accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY }); const upload = multer({ storage: multerS3({ s3: s3, bucket: process.env.S3_BUCKET, metadata: function (req, file, cb) { cb(null, {fieldName: file.fieldname}); }, key: function (req, file, cb) { cb(null, Date.now().toString() + '-' + file.originalname) } }) });
Error Handling
Use centralized error handling
// Error handling middleware (must be last) app.use((err, req, res, next) => { console.error(err.stack); // Don't expose stack traces in production if (process.env.NODE_ENV === 'production') { res.status(err.status || 500).json({ error: 'Internal server error' }); } else { res.status(err.status || 500).json({ error: err.message, stack: err.stack }); } });Handle async errors
// Wrapper for async route handlers const asyncHandler = fn => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; app.get('/users', asyncHandler(async (req, res) => { const users = await User.find(); res.json(users); }));
Environment Variables & Secrets
Use environment variables
require('dotenv').config(); const config = { port: process.env.PORT || 3000, dbUri: process.env.DATABASE_URL, jwtSecret: process.env.JWT_SECRET, sessionSecret: process.env.SESSION_SECRET };Store secrets in Heroku config
heroku config:set JWT_SECRET='...' heroku config:set SESSION_SECRET='...' heroku config:set DATABASE_URL='...'Never commit .env files
- Add to .gitignore
- Use .env.example for template
Logging & Monitoring
Implement structured logging
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.Console() ] }); app.use((req, res, next) => { logger.info(`${req.method} ${req.path}`); next(); });Don’t log sensitive data
- Passwords, tokens, API keys
- Credit card numbers
- Personal information
Use Morgan for HTTP logging
const morgan = require('morgan'); app.use(morgan('combined'));
Dependency Security
Audit npm packages regularly
npm audit npm audit fixKeep dependencies updated
npm outdated npm updateUse npm lockfile
- Commit package-lock.json
- Use
npm ciin production
Remove unused dependencies
npm prune --production
Heroku Deployment
Create Procfile
web: node server.jsSpecify Node.js version
// package.json { "engines": { "node": "18.x", "npm": "9.x" } }Use production mode
heroku config:set NODE_ENV=productionConfigure process manager (optional)
web: node_modules/.bin/pm2 start server.js -i max --no-daemonEnable compression
const compression = require('compression'); app.use(compression());
Additional Security Middleware
Install essential security packages
npm install helmet express-rate-limit express-mongo-sanitize hpp express-validatorPrevent parameter pollution
const hpp = require('hpp'); app.use(hpp());
Testing & Pre-Deployment
Test security headers
- Use securityheaders.com
- Verify CSP, HSTS, X-Frame-Options
Run security linters
npm install --save-dev eslint-plugin-securityTest on Heroku staging
heroku create myapp-staging git push staging main
Essential Express.js Security Packages
helmet- Security headersexpress-rate-limit- Rate limitingexpress-validator- Input validationexpress-mongo-sanitize- NoSQL injection preventionhpp- HTTP parameter pollution protectioncors- CORS configurationcsurf- CSRF protectionbcrypt- Password hashingjsonwebtoken- JWT authenticationpassport- Authentication strategies
Additional Resources
- Express.js Security Best Practices
- Node.js Security Checklist
- OWASP Node.js Security Cheat Sheet
- Heroku Node.js Support