Security Best Practices
Security is paramount in production applications. Learn essential security practices for protecting Node.js applications and user data.
Challenge
Section titled “Challenge”Implement comprehensive security measures to protect against common vulnerabilities and attacks in Node.js applications.
Explanation
Section titled “Explanation”Common Vulnerabilities
Section titled “Common Vulnerabilities”- Injection Attacks: SQL injection, command injection
- Authentication: Weak password handling, token issues
- Authorization: Insufficient permission checks
- Data Exposure: Sensitive data leaks
- Dependencies: Vulnerable packages
- CORS: Cross-origin request issues
Security Layers
Section titled “Security Layers”- Input Validation: Sanitize and validate inputs
- Encryption: Protect data in transit and at rest
- Authentication: Secure user verification
- Authorization: Fine-grained access control
- Auditing: Track security-related events
Code Examples
Section titled “Code Examples”1. Input Validation and Sanitization
Section titled “1. Input Validation and Sanitization”const express = require('express');const validator = require('express-validator');const DOMPurify = require('isomorphic-dompurify');
const app = express();
// Validation middlewareconst validateUser = [ validator.body('email').isEmail().normalizeEmail(), validator.body('username') .isLength({ min: 3, max: 20 }) .matches(/^[a-zA-Z0-9_]+$/), validator.body('password') .isLength({ min: 12 }) .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/),];
app.post('/register', validateUser, (req, res) => { const errors = validator.validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); }
// Sanitize inputs const cleanEmail = DOMPurify.sanitize(req.body.email); const cleanUsername = DOMPurify.sanitize(req.body.username);
// Process registration res.json({ message: 'Registration successful' });});
// SQL injection preventionapp.get('/users/:id', (req, res) => { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ error: 'Invalid ID' }); }
// Use parameterized queries const query = 'SELECT * FROM users WHERE id = $1'; database.query(query, [id], (err, result) => { if (err) return res.status(500).json({ error: err.message }); res.json(result.rows); });});2. Secure Password Handling
Section titled “2. Secure Password Handling”const bcrypt = require('bcrypt');const crypto = require('crypto');
class PasswordManager { async hashPassword(password) { const saltRounds = 12; // Bcrypt cost factor return bcrypt.hash(password, saltRounds); }
async verifyPassword(password, hash) { return bcrypt.compare(password, hash); }
generateTemporaryPassword() { return crypto.randomBytes(16).toString('hex'); }
validatePasswordStrength(password) { const checks = { length: password.length >= 12, uppercase: /[A-Z]/.test(password), lowercase: /[a-z]/.test(password), numbers: /\d/.test(password), special: /[@$!%*?&]/.test(password), };
return { isStrong: Object.values(checks).every(v => v), checks, }; }}
// Usageconst passwordManager = new PasswordManager();
// During registrationapp.post('/register', async (req, res) => { const { password } = req.body;
const strength = passwordManager.validatePasswordStrength(password); if (!strength.isStrong) { return res.status(400).json({ error: 'Password does not meet requirements', checks: strength.checks, }); }
const hash = await passwordManager.hashPassword(password); // Store hash in database});
// During loginapp.post('/login', async (req, res) => { const { email, password } = req.body;
const user = await database.users.findByEmail(email); if (!user) { // Don't reveal if email exists return res.status(401).json({ error: 'Invalid credentials' }); }
const isValid = await passwordManager.verifyPassword(password, user.passwordHash); if (!isValid) { return res.status(401).json({ error: 'Invalid credentials' }); }
// Issue JWT token res.json({ token: generateToken(user) });});3. CORS and HTTPS Security
Section titled “3. CORS and HTTPS Security”const express = require('express');const cors = require('cors');const helmet = require('helmet');
const app = express();
// Security headersapp.use(helmet());
// CORS configurationconst corsOptions = { origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], maxAge: 3600,};
app.use(cors(corsOptions));
// HTTPS enforcementapp.use((req, res, next) => { if (process.env.NODE_ENV === 'production' && req.header('x-forwarded-proto') !== 'https') { return res.redirect(301, `https://${req.header('host')}${req.url}`); } next();});
// Content Security Policyapp.use(helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", 'https:'], },}));
// Prevent clickjackingapp.use(helmet.frameguard({ action: 'deny' }));4. JWT Token Security
Section titled “4. JWT Token Security”const jwt = require('jsonwebtoken');
class TokenManager { constructor(secret = process.env.JWT_SECRET, expiresIn = '24h') { this.secret = secret; this.expiresIn = expiresIn; }
generateToken(payload, options = {}) { const tokenPayload = { ...payload, iat: Math.floor(Date.now() / 1000), };
return jwt.sign(tokenPayload, this.secret, { expiresIn: this.expiresIn, ...options, }); }
verifyToken(token) { try { return jwt.verify(token, this.secret); } catch (error) { throw new Error(`Invalid token: ${error.message}`); } }
generateRefreshToken(userId) { return jwt.sign({ userId, type: 'refresh' }, this.secret, { expiresIn: '7d', }); }
revokeToken(token) { // Store revoked tokens in Redis or database const decoded = jwt.decode(token); const ttl = decoded.exp - Math.floor(Date.now() / 1000);
if (ttl > 0) { // Store in cache with TTL cache.set(`revoked:${token}`, 'true', ttl); } }
isTokenRevoked(token) { return cache.get(`revoked:${token}`) !== null; }}
// Middlewarefunction authMiddleware(req, res, next) { const token = req.headers.authorization?.split(' ')[1];
if (!token) { return res.status(401).json({ error: 'No token provided' }); }
const tokenManager = new TokenManager();
try { if (tokenManager.isTokenRevoked(token)) { return res.status(401).json({ error: 'Token has been revoked' }); }
const payload = tokenManager.verifyToken(token); req.user = payload; next(); } catch (error) { res.status(401).json({ error: error.message }); }}5. Dependency Vulnerability Scanning
Section titled “5. Dependency Vulnerability Scanning”# Check for vulnerabilitiesnpm audit
# Fix vulnerabilitiesnpm audit fix
# Check for outdated packagesnpm outdated
# Update packagesnpm update6. Rate Limiting and DDoS Protection
Section titled “6. Rate Limiting and DDoS Protection”const rateLimit = require('express-rate-limit');const RedisStore = require('rate-limit-redis');const redis = require('redis');
const client = redis.createClient();
const limiter = rateLimit({ store: new RedisStore({ client: client, prefix: 'rate-limit:', }), windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP', standardHeaders: true, legacyHeaders: false,});
// Apply globallyapp.use(limiter);
// Stricter limit for authconst authLimiter = rateLimit({ store: new RedisStore({ client: client, prefix: 'auth-rate-limit:', }), windowMs: 15 * 60 * 1000, max: 5, // 5 attempts skipSuccessfulRequests: true,});
app.post('/login', authLimiter, (req, res) => { // Login logic});Best Practices
Section titled “Best Practices”-
Input Validation
- Validate all inputs
- Sanitize before processing
- Use parameterized queries
- Implement allowlists
-
Authentication
- Use strong password policies
- Implement MFA
- Secure token storage
- Handle token refresh properly
-
Authorization
- Implement least privilege
- Check permissions on every request
- Use role-based access control
- Audit access attempts
-
Data Protection
- Encrypt sensitive data
- Use HTTPS/TLS
- Implement secure headers
- Handle PII carefully
-
Dependency Management
- Keep dependencies updated
- Run security audits regularly
- Remove unused packages
- Review package advisories
Real-World Applications
Section titled “Real-World Applications”- User Authentication: Secure login systems
- API Protection: Rate limiting and validation
- Data Encryption: Protecting sensitive information
- Compliance: Meeting GDPR, PCI-DSS requirements
- Threat Detection: Identifying suspicious activity