Part 19 – Security Essentials

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

Leave a Comment

Your email address will not be published. Required fields are marked *