Chapter 17: Security Best Practices
XSS Prevention
// ❌ Dangerous - XSS vulnerability function dangerousRender(userInput) { document.innerHTML = `<div>Hello ${userInput}</div>`; // If userInput is "<script>alert('XSS')</script>", it will execute! } // ✅ Safe - Escape user input function safeRender(userInput) { const escaped = userInput .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); document.getElementById('content').innerHTML = `<div>Hello ${escaped}</div>`; } // ✅ Even safer - Use textContent function safestRender(userInput) { const div = document.createElement('div'); div.textContent = `Hello ${userInput}`; document.getElementById('content').appendChild(div); } // Sanitization library example (like DOMPurify) function sanitizeHTML(html) { // This is a simplified version - use a real library in production const allowedTags = ['p', 'br', 'strong', 'em']; const allowedAttributes = ['class']; // Implementation would parse and clean HTML return html; // Simplified }
CSRF Protection
// CSRF token handling class CSRFProtection { constructor() { this.token = this.generateToken(); } generateToken() { return Array.from(crypto.getRandomValues(new Uint8Array(32))) .map(b => b.toString(16).padStart(2, '0')) .join(''); } async makeSecureRequest(url, options = {}) { const headers = { 'Content-Type': 'application/json', 'X-CSRF-Token': this.token, ...options.headers }; return fetch(url, { ...options, headers, credentials: 'same-origin' // Include cookies }); } } const csrf = new CSRFProtection();
Input Validation
// Comprehensive input validation class Validator { static email(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } static password(password) { // At least 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; return passwordRegex.test(password); } static sanitizeString(str, maxLength = 255) { if (typeof str !== 'string') return ''; return str.trim().slice(0, maxLength); } static isValidJSON(str) { try { JSON.parse(str); return true; } catch { return false; } } static validateUserData(data) { const errors = []; if (!data.email || !this.email(data.email)) { errors.push('Invalid email address'); } if (!data.password || !this.password(data.password)) { errors.push('Password must be at least 8 characters with mixed case, numbers, and symbols'); } if (!data.name || data.name.trim().length < 2) { errors.push('Name must be at least 2 characters'); } return { isValid: errors.length === 0, errors, sanitized: { email: this.sanitizeString(data.email), name: this.sanitizeString(data.name), // Don't sanitize password - hash it instead } }; } }
Advanced Security Patterns
Content Security Policy (CSP) Helper
class CSPHelper { static generateNonce() { return Array.from(crypto.getRandomValues(new Uint8Array(16))) .map(b => b.toString(16).padStart(2, '0')) .join(''); } static createSecureScript(code, nonce) { const script = document.createElement('script'); script.nonce = nonce; script.textContent = code; return script; } static loadExternalScript(src, nonce) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.nonce = nonce; script.onload = resolve; script.onerror = reject; // Verify source is from allowed domains const allowedDomains = ['cdn.example.com', 'trusted-scripts.com']; const url = new URL(src); if (!allowedDomains.includes(url.hostname)) { reject(new Error(`Script from unauthorized domain: ${url.hostname}`)); return; } document.head.appendChild(script); }); } }
Secure Data Storage
class SecureStorage { constructor(key) { this.encryptionKey = key; } // Simple encryption (use a proper library in production) encrypt(data) { const jsonString = JSON.stringify(data); // This is a very basic encoding - use proper encryption return btoa(jsonString); } decrypt(encryptedData) { try { const jsonString = atob(encryptedData); return JSON.parse(jsonString); } catch { return null; } } setSecureItem(key, value) { const encrypted = this.encrypt(value); localStorage.setItem(key, encrypted); } getSecureItem(key) { const encrypted = localStorage.getItem(key); return encrypted ? this.decrypt(encrypted) : null; } removeSecureItem(key) { localStorage.removeItem(key); } // Clear sensitive data clearSensitiveData() { const sensitiveKeys = ['authToken', 'userCredentials', 'sessionData']; sensitiveKeys.forEach(key => this.removeSecureItem(key)); } } // Usage const secureStorage = new SecureStorage('your-encryption-key'); secureStorage.setSecureItem('userToken', { token: 'abc123', expires: Date.now() + 3600000 });
SQL Injection Prevention (for Node.js)
// Safe database query patterns class DatabaseManager { constructor(dbConnection) { this.db = dbConnection; } // ❌ Dangerous - SQL injection vulnerability async dangerousQuery(userId) { const query = `SELECT * FROM users WHERE id = ${userId}`; return this.db.query(query); // Never do this! } // ✅ Safe - Parameterized queries async safeQuery(userId) { const query = 'SELECT * FROM users WHERE id = ?'; return this.db.query(query, [userId]); } // ✅ Safe search with validation async searchUsers(searchTerm, limit = 10) { // Validate and sanitize input if (typeof searchTerm !== 'string' || searchTerm.length < 2) { throw new Error('Invalid search term'); } const sanitizedTerm = searchTerm.replace(/[%_]/g, '\\$&'); // Escape SQL wildcards const query = 'SELECT id, name, email FROM users WHERE name LIKE ? LIMIT ?'; return this.db.query(query, [`%${sanitizedTerm}%`, parseInt(limit)]); } // Input validation for complex queries async updateUser(userId, userData) { const allowedFields = ['name', 'email', 'age']; const updates = {}; // Only allow whitelisted fields for (const [key, value] of Object.entries(userData)) { if (allowedFields.includes(key)) { updates[key] = value; } } if (Object.keys(updates).length === 0) { throw new Error('No valid fields to update'); } const fields = Object.keys(updates).map(field => `${field} = ?`).join(', '); const values = [...Object.values(updates), userId]; const query = `UPDATE users SET ${fields} WHERE id = ?`; return this.db.query(query, values); } }
Authentication and Authorization
class AuthManager { constructor() { this.tokenKey = 'authToken'; this.refreshTokenKey = 'refreshToken'; } // Secure token storage setTokens(accessToken, refreshToken) { // Store with expiration const tokenData = { token: accessToken, expires: Date.now() + (15 * 60 * 1000) // 15 minutes }; localStorage.setItem(this.tokenKey, JSON.stringify(tokenData)); // Refresh token (longer lived, httpOnly cookie preferred) localStorage.setItem(this.refreshTokenKey, refreshToken); } getAccessToken() { const tokenData = localStorage.getItem(this.tokenKey); if (!tokenData) return null; try { const parsed = JSON.parse(tokenData); if (Date.now() > parsed.expires) { this.clearTokens(); return null; } return parsed.token; } catch { return null; } } async refreshAccessToken() { const refreshToken = localStorage.getItem(this.refreshTokenKey); if (!refreshToken) { throw new Error('No refresh token available'); } try { const response = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) }); if (!response.ok) { throw new Error('Token refresh failed'); } const { accessToken, refreshToken: newRefreshToken } = await response.json(); this.setTokens(accessToken, newRefreshToken); return accessToken; } catch (error) { this.clearTokens(); throw error; } } clearTokens() { localStorage.removeItem(this.tokenKey); localStorage.removeItem(this.refreshTokenKey); } // Secure API request with automatic token refresh async secureRequest(url, options = {}) { let token = this.getAccessToken(); if (!token) { try { token = await this.refreshAccessToken(); } catch { throw new Error('Authentication required'); } } const response = await fetch(url, { ...options, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', ...options.headers } }); if (response.status === 401) { // Token might be expired, try refresh once try { token = await this.refreshAccessToken(); return fetch(url, { ...options, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', ...options.headers } }); } catch { this.clearTokens(); throw new Error('Authentication failed'); } } return response; } }
Rate Limiting and DDoS Protection
class RateLimiter { constructor(maxRequests = 100, windowMs = 60000) { this.maxRequests = maxRequests; this.windowMs = windowMs; this.requests = new Map(); } checkLimit(identifier) { const now = Date.now(); const windowStart = now - this.windowMs; // Clean old requests if (this.requests.has(identifier)) { const userRequests = this.requests.get(identifier) .filter(timestamp => timestamp > windowStart); this.requests.set(identifier, userRequests); } const userRequests = this.requests.get(identifier) || []; if (userRequests.length >= this.maxRequests) { return { allowed: false, retryAfter: Math.ceil((userRequests[0] - windowStart) / 1000) }; } userRequests.push(now); this.requests.set(identifier, userRequests); return { allowed: true, remaining: this.maxRequests - userRequests.length }; } // Middleware-style usage createMiddleware() { return (req, res, next) => { const identifier = req.ip || req.connection.remoteAddress; const result = this.checkLimit(identifier); if (!result.allowed) { res.status(429).json({ error: 'Too many requests', retryAfter: result.retryAfter }); return; } res.setHeader('X-RateLimit-Remaining', result.remaining); next(); }; } }
Security Headers and Configuration
class SecurityHeaders { static getSecureHeaders() { return { // Prevent XSS attacks 'X-XSS-Protection': '1; mode=block', // Prevent content-type sniffing 'X-Content-Type-Options': 'nosniff', // Prevent clickjacking 'X-Frame-Options': 'DENY', // HTTPS only 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', // Content Security Policy 'Content-Security-Policy': [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "connect-src 'self'", "font-src 'self'", "object-src 'none'", "media-src 'self'", "frame-src 'none'" ].join('; '), // Referrer policy 'Referrer-Policy': 'strict-origin-when-cross-origin' }; } static applyToResponse(response) { const headers = this.getSecureHeaders(); Object.entries(headers).forEach(([key, value]) => { response.setHeader(key, value); }); } }
Final Security Checklist
// Security audit checklist const SecurityAudit = { checkInputValidation() { // Verify all user inputs are validated and sanitized return true; }, checkAuthentication() { // Verify secure authentication implementation return true; }, checkAuthorization() { // Verify proper access controls return true; }, checkDataEncryption() { // Verify sensitive data is encrypted return true; }, checkSecureHeaders() { // Verify security headers are implemented return true; }, checkDependencies() { // Check for vulnerable dependencies // npm audit, yarn audit return true; }, generateReport() { const checks = [ { name: 'Input Validation', passed: this.checkInputValidation() }, { name: 'Authentication', passed: this.checkAuthentication() }, { name: 'Authorization', passed: this.checkAuthorization() }, { name: 'Data Encryption', passed: this.checkDataEncryption() }, { name: 'Security Headers', passed: this.checkSecureHeaders() }, { name: 'Dependencies', passed: this.checkDependencies() } ]; const passed = checks.filter(check => check.passed).length; const total = checks.length; return { score: `${passed}/${total}`, percentage: Math.round((passed / total) * 100), checks, recommendations: this.getRecommendations(checks) }; }, getRecommendations(checks) { const failed = checks.filter(check => !check.passed); return failed.map(check => `Improve ${check.name}`); } };
Remember: Security is an ongoing process, not a one-time setup. Regularly audit your code, keep dependencies updated, and stay informed about new vulnerabilities and best practices.
Previous Chapter: Chapter 16: Testing and Debugging
Table of Contents: JavaScript Guide
🎉 Congratulations! You've completed the JavaScript Basics & Interview Guide! 🎉
