Securing Node.js applications requires defense in depth. This comprehensive guide covers authentication, data protection, dependency security, and hardening techniques for production applications.
1. Common Node.js Vulnerabilities
OWASP Top 10 Risks
- Injection attacks (SQL, NoSQL, Command)
- Broken authentication
- Sensitive data exposure
- XML external entities (XXE)
- Broken access control
Node-Specific Concerns
- Prototype pollution
- Unsafe deserialization
- Regular expression DoS
- Unvalidated redirects
- Server-side request forgery
2. Secure Application Setup
Express Security Middleware
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const app = express();
// Essential security headers
app.use(helmet());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per window
});
app.use(limiter);
// Disable powered-by header
app.disable('x-powered-by');
// CORS configuration
app.use(cors({
origin: ['https://yourdomain.com'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
3. Authentication Hardening
Password Security
const bcrypt = require('bcrypt');
const saltRounds = 12;
// Hashing passwords
async function hashPassword(plaintext) {
return await bcrypt.hash(plaintext, saltRounds);
}
// Verification
async function verifyPassword(plaintext, hash) {
return await bcrypt.compare(plaintext, hash);
}
// Additional protections:
// - Minimum length requirements
// - Common password checks
// - Rate limiting attempts
// - Multi-factor authentication
JWT Best Practices
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
// Generate strong secret
const secret = crypto.randomBytes(64).toString('hex');
// Token creation
function createToken(user) {
return jwt.sign(
{ userId: user.id },
secret,
{
expiresIn: '15m', // Short-lived access token
issuer: 'yourdomain.com',
algorithm: 'HS256'
}
);
}
// Token verification
function verifyToken(token) {
return jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'yourdomain.com'
});
}
4. Data Validation and Sanitization
Input Validation
const { body, validationResult } = require('express-validator');
app.post('/register',
[
body('email').isEmail().normalizeEmail(),
body('password')
.isLength({ min: 12 })
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])/),
body('username')
.trim()
.isLength({ min: 3, max: 20 })
.escape()
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process valid data
}
);
NoSQL Injection Protection
// Dangerous (vulnerable to injection)
const query = {
username: req.query.user,
isAdmin: false
};
User.find(query);
// Safe approaches:
// 1. Mongoose built-in sanitization
User.findOne({ username: req.query.user, isAdmin: false });
// 2. Explicit validation
const username = validator.escape(req.query.user);
User.findOne({ username, isAdmin: false });
// 3. Parameterized queries
User.findOne({
username: { $eq: req.query.user },
isAdmin: { $eq: false }
});
5. Secure Dependencies
Dependency Auditing
# Audit dependencies
npm audit
# Fix vulnerabilities
npm audit fix
# Force update if needed
npm audit fix --force
# CI/CD integration
# package.json
"scripts": {
"preinstall": "npx npm-force-resolutions"
},
"resolutions": {
"**/lodash": "4.17.21"
}
Lockfile Security
- Always commit
package-lock.json
- Configure CI to fail on outdated packages
- Use
npm ci
in production - Consider
npm shrinkwrap
for critical apps - Automate updates with Dependabot/Renovate
6. Advanced Protection
CSRF Protection
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
const csrfProtection = csrf({
cookie: true,
value: req => req.headers['x-csrf-token']
});
// Apply to routes
app.get('/form', csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
app.post('/process', csrfProtection, (req, res) => {
// Protected from CSRF
});
Content Security Policy
const csp = require('helmet-csp');
app.use(csp({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", 'trusted.cdn.com'],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'images.example.com'],
fontSrc: ["'self'", 'fonts.example.com'],
connectSrc: ["'self'", 'api.example.com'],
frameAncestors: ["'none'"],
formAction: ["'self'"],
upgradeInsecureRequests: []
}
}));
7. Monitoring and Response
Security Headers Check
# Check headers with curl
curl -I https://yourdomain.com
# Should include:
# X-Content-Type-Options: nosniff
# X-Frame-Options: DENY
# X-XSS-Protection: 1; mode=block
# Content-Security-Policy: ...
# Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Incident Response
- Log all security-relevant events
- Implement automated alerting
- Maintain incident response plan
- Regularly rotate secrets and keys
- Conduct penetration testing
Next: Advanced Patterns and Practices →
Security Checklist
- ✅ All dependencies audited and updated
- ✅ Input validation and output encoding
- ✅ Secure authentication implemented
- ✅ Proper error handling (no stack traces)
- ✅ HTTPS enforced with HSTS
- ✅ Security headers configured
- ✅ Regular backups with encryption