JavaScript Security
JavaScript Security
Section titled “JavaScript Security”Security is paramount in modern web development. This comprehensive guide covers essential security practices, common vulnerabilities, and defensive programming techniques for JavaScript applications.
Common Security Vulnerabilities
Section titled “Common Security Vulnerabilities”Cross-Site Scripting (XSS)
Section titled “Cross-Site Scripting (XSS)”XSS attacks occur when malicious scripts are injected into trusted websites.
// VULNERABLE: Direct DOM manipulation with user inputfunction displayUserName(userName) { document.getElementById('greeting').innerHTML = `Hello, ${userName}!`; // If userName contains <script>alert('XSS')</script>, it will execute!}
// SECURE: Use textContent or proper escapingfunction displayUserNameSecure(userName) { document.getElementById('greeting').textContent = `Hello, ${userName}!`; // Or use innerHTML with proper escaping document.getElementById('greeting').innerHTML = `Hello, ${escapeHtml(userName)}!`;}
function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML;}
// SECURE: Use template engines with auto-escapingconst template = `<div>Hello, {{username}}</div>`; // Handlebars auto-escapes
// SECURE: Content Security Policy headers// Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
Cross-Site Request Forgery (CSRF)
Section titled “Cross-Site Request Forgery (CSRF)”CSRF attacks trick users into performing unwanted actions on websites where they’re authenticated.
// VULNERABLE: Simple form submission without CSRF protectionasync function deleteAccount() { await fetch('/api/account/delete', { method: 'DELETE', credentials: 'include' // Sends cookies });}
// SECURE: CSRF token implementationclass CSRFProtection { constructor() { this.token = this.getCSRFToken(); }
getCSRFToken() { return document.querySelector('meta[name="csrf-token"]').getAttribute('content'); }
async secureRequest(url, options = {}) { const headers = { 'Content-Type': 'application/json', 'X-CSRF-Token': this.token, ...options.headers };
return fetch(url, { ...options, headers, credentials: 'include' }); }}
const csrf = new CSRFProtection();
// Usageasync function deleteAccountSecure() { try { const response = await csrf.secureRequest('/api/account/delete', { method: 'DELETE' });
if (!response.ok) { throw new Error('Delete failed'); } } catch (error) { console.error('Account deletion failed:', error); }}
// Server-side CSRF validation (Express.js example)const csrf = require('csurf');app.use(csrf({ cookie: true }));
app.get('/form', (req, res) => { res.render('form', { csrfToken: req.csrfToken() });});
SQL Injection Prevention
Section titled “SQL Injection Prevention”Even in JavaScript applications, SQL injection can occur on the backend.
// VULNERABLE: String concatenationasync function getUserByIdVulnerable(userId) { const query = `SELECT * FROM users WHERE id = ${userId}`; return database.query(query); // If userId is "1 OR 1=1", returns all users!}
// SECURE: Parameterized queriesasync function getUserByIdSecure(userId) { const query = 'SELECT * FROM users WHERE id = ?'; return database.query(query, [userId]);}
// SECURE: ORM with built-in protectionasync function getUserWithORM(userId) { return User.findById(userId); // Sequelize, TypeORM, etc.}
// Input validation and sanitizationconst Joi = require('joi');
const userIdSchema = Joi.number().integer().positive().required();
async function validateAndGetUser(userId) { const { error, value } = userIdSchema.validate(userId);
if (error) { throw new Error('Invalid user ID'); }
return getUserByIdSecure(value);}
Input Validation and Sanitization
Section titled “Input Validation and Sanitization”Client-Side Validation
Section titled “Client-Side Validation”class InputValidator { static email(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }
static password(password) { const minLength = 8; const hasUpperCase = /[A-Z]/.test(password); const hasLowerCase = /[a-z]/.test(password); const hasNumbers = /\d/.test(password); const hasNonalphas = /\W/.test(password);
return password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasNonalphas; }
static sanitizeString(input) { return input .replace(/[<>]/g, '') // Remove angle brackets .trim() .slice(0, 255); // Limit length }
static isNumeric(value) { return !isNaN(parseFloat(value)) && isFinite(value); }
static isAlphanumeric(input) { return /^[a-zA-Z0-9]+$/.test(input); }}
// Comprehensive form validationclass FormValidator { constructor(form) { this.form = form; this.errors = {}; }
validateField(fieldName, value, rules) { this.errors[fieldName] = [];
rules.forEach(rule => { if (rule.type === 'required' && !value.trim()) { this.errors[fieldName].push('This field is required'); }
if (rule.type === 'email' && value && !InputValidator.email(value)) { this.errors[fieldName].push('Invalid email format'); }
if (rule.type === 'password' && value && !InputValidator.password(value)) { this.errors[fieldName].push('Password must be at least 8 characters with uppercase, lowercase, number, and special character'); }
if (rule.type === 'minLength' && value.length < rule.value) { this.errors[fieldName].push(`Minimum length is ${rule.value} characters`); }
if (rule.type === 'maxLength' && value.length > rule.value) { this.errors[fieldName].push(`Maximum length is ${rule.value} characters`); } });
// Remove empty error arrays if (this.errors[fieldName].length === 0) { delete this.errors[fieldName]; } }
validate(formData, validationRules) { this.errors = {};
Object.keys(validationRules).forEach(fieldName => { const value = formData[fieldName] || ''; const rules = validationRules[fieldName]; this.validateField(fieldName, value, rules); });
return { isValid: Object.keys(this.errors).length === 0, errors: this.errors }; }}
// Usage exampleconst validator = new FormValidator();const formData = { email: 'user@example.com', password: 'weak', confirmPassword: 'weak'};
const rules = { email: [ { type: 'required' }, { type: 'email' } ], password: [ { type: 'required' }, { type: 'password' }, { type: 'minLength', value: 8 } ]};
const validation = validator.validate(formData, rules);
Authentication and Authorization
Section titled “Authentication and Authorization”JWT Implementation
Section titled “JWT Implementation”// JWT token managementclass JWTManager { constructor() { this.tokenKey = 'auth_token'; this.refreshKey = 'refresh_token'; }
setTokens(accessToken, refreshToken) { // Store in httpOnly cookies (preferred) or secure storage this.setSecureCookie(this.tokenKey, accessToken, { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 15 * 60 * 1000 // 15 minutes });
this.setSecureCookie(this.refreshKey, refreshToken, { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days }); }
getToken() { return this.getCookie(this.tokenKey); }
removeTokens() { this.deleteCookie(this.tokenKey); this.deleteCookie(this.refreshKey); }
async refreshToken() { try { const refreshToken = this.getCookie(this.refreshKey);
const response = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refreshToken }), credentials: 'include' });
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.removeTokens(); window.location.href = '/login'; throw error; } }
setSecureCookie(name, value, options) { const optionsString = Object.entries(options) .map(([key, val]) => `${key}=${val}`) .join('; ');
document.cookie = `${name}=${value}; ${optionsString}`; }
getCookie(name) { const match = document.cookie.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`); return match ? match.pop() : null; }
deleteCookie(name) { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; }}
// Secure API client with automatic token refreshclass SecureAPIClient { constructor() { this.jwtManager = new JWTManager(); this.isRefreshing = false; this.refreshPromise = null; }
async request(url, options = {}) { const token = this.jwtManager.getToken();
const config = { ...options, headers: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '', ...options.headers }, credentials: 'include' };
try { let response = await fetch(url, config);
// Handle token expiration if (response.status === 401 && token) { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshPromise = this.jwtManager.refreshToken(); }
await this.refreshPromise; this.isRefreshing = false; this.refreshPromise = null;
// Retry original request with new token const newToken = this.jwtManager.getToken(); config.headers.Authorization = `Bearer ${newToken}`; response = await fetch(url, config); }
return response; } catch (error) { this.isRefreshing = false; this.refreshPromise = null; throw error; } }
async get(url, options = {}) { return this.request(url, { ...options, method: 'GET' }); }
async post(url, data, options = {}) { return this.request(url, { ...options, method: 'POST', body: JSON.stringify(data) }); }}
Role-Based Access Control
Section titled “Role-Based Access Control”class RBACManager { constructor(userRoles, permissions) { this.userRoles = userRoles; this.permissions = permissions; }
hasPermission(permission) { return this.userRoles.some(role => this.permissions[role] && this.permissions[role].includes(permission) ); }
hasRole(role) { return this.userRoles.includes(role); }
canAccess(resource, action = 'read') { const permission = `${resource}:${action}`; return this.hasPermission(permission); }}
// Permission definitionsconst permissions = { admin: ['users:read', 'users:write', 'users:delete', 'posts:read', 'posts:write', 'posts:delete'], moderator: ['posts:read', 'posts:write', 'posts:delete', 'users:read'], user: ['posts:read', 'profile:read', 'profile:write'], guest: ['posts:read']};
// Usageconst rbac = new RBACManager(['user'], permissions);
function SecureComponent({ children, requiredPermission }) { if (!rbac.hasPermission(requiredPermission)) { return <div>Access denied</div>; } return children;}
// React component usage<SecureComponent requiredPermission="users:write"> <UserEditForm /></SecureComponent>
Data Protection
Section titled “Data Protection”Encryption and Hashing
Section titled “Encryption and Hashing”// Client-side encryption (for sensitive data)class ClientEncryption { async generateKey() { return crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); }
async encrypt(data, key) { const iv = crypto.getRandomValues(new Uint8Array(12)); const encoder = new TextEncoder(); const encodedData = encoder.encode(data);
const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, encodedData );
return { encrypted: Array.from(new Uint8Array(encrypted)), iv: Array.from(iv) }; }
async decrypt(encryptedData, key, iv) { const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: new Uint8Array(iv) }, key, new Uint8Array(encryptedData) );
const decoder = new TextDecoder(); return decoder.decode(decrypted); }}
// Secure password hashing (server-side with Node.js)const bcrypt = require('bcrypt');
class PasswordManager { static async hashPassword(password) { const saltRounds = 12; return bcrypt.hash(password, saltRounds); }
static async verifyPassword(password, hash) { return bcrypt.compare(password, hash); }
static generateSecurePassword(length = 16) { const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'; let password = '';
for (let i = 0; i < length; i++) { password += charset.charAt(Math.floor(Math.random() * charset.length)); }
return password; }}
// Usageasync function registerUser(email, password) { const hashedPassword = await PasswordManager.hashPassword(password);
// Store hashedPassword in database return User.create({ email, password: hashedPassword });}
async function loginUser(email, password) { const user = await User.findOne({ email });
if (!user) { throw new Error('User not found'); }
const isValid = await PasswordManager.verifyPassword(password, user.password);
if (!isValid) { throw new Error('Invalid password'); }
return user;}
Content Security Policy (CSP)
Section titled “Content Security Policy (CSP)”CSP Implementation
Section titled “CSP Implementation”// CSP header configurationconst cspDirectives = { 'default-src': ["'self'"], 'script-src': [ "'self'", "'unsafe-inline'", // Avoid if possible 'https://cdn.jsdelivr.net', 'https://unpkg.com' ], 'style-src': [ "'self'", "'unsafe-inline'", // Often necessary for CSS 'https://fonts.googleapis.com' ], 'img-src': [ "'self'", 'data:', 'https:' ], 'font-src': [ "'self'", 'https://fonts.gstatic.com' ], 'connect-src': [ "'self'", 'https://api.example.com' ], 'frame-src': ["'none'"], 'object-src': ["'none'"], 'base-uri': ["'self'"], 'form-action': ["'self'"]};
function generateCSPHeader() { return Object.entries(cspDirectives) .map(([directive, sources]) => `${directive} ${sources.join(' ')}`) .join('; ');}
// Express.js middlewarefunction cspMiddleware(req, res, next) { const csp = generateCSPHeader(); res.setHeader('Content-Security-Policy', csp); next();}
// CSP violation reportingfunction setupCSPReporting() { window.addEventListener('securitypolicyviolation', (event) => { const violation = { blockedURI: event.blockedURI, documentURI: event.documentURI, effectiveDirective: event.effectiveDirective, originalPolicy: event.originalPolicy, referrer: event.referrer, violatedDirective: event.violatedDirective, timestamp: Date.now() };
// Send violation report to server fetch('/api/csp-violation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(violation) }); });}
Secure Communication
Section titled “Secure Communication”HTTPS and Certificate Pinning
Section titled “HTTPS and Certificate Pinning”// Force HTTPS redirectfunction enforceHTTPS() { if (location.protocol !== 'https:' && location.hostname !== 'localhost') { location.replace('https:' + window.location.href.substring(window.location.protocol.length)); }}
// Certificate pinning (for mobile apps)class CertificatePinner { constructor(pinnedCertificates) { this.pinnedCerts = pinnedCertificates; }
async verifyConnection(url) { try { const response = await fetch(url, { method: 'HEAD' });
// In a real implementation, you would verify the certificate // This is a simplified example const cert = this.getCertificateInfo(response);
if (!this.isPinnedCertificate(cert)) { throw new Error('Certificate pinning validation failed'); }
return true; } catch (error) { console.error('Certificate pinning failed:', error); return false; } }
getCertificateInfo(response) { // Extract certificate information // In practice, this would involve more complex certificate parsing return response.headers.get('x-certificate-fingerprint'); }
isPinnedCertificate(cert) { return this.pinnedCerts.includes(cert); }}
// HSTS (HTTP Strict Transport Security)function setHSTSHeader(res) { res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');}
Security Auditing
Section titled “Security Auditing”Automated Security Scanning
Section titled “Automated Security Scanning”// Dependency vulnerability scanningconst auditScript = `npm audit --audit-level=moderatenpm audit fix --force`;
// Custom security audit functionclass SecurityAuditor { constructor() { this.vulnerabilities = []; }
checkForInsecurePatterns(code) { const patterns = [ { pattern: /eval\s*\(/g, severity: 'high', message: 'Use of eval() is dangerous and should be avoided' }, { pattern: /innerHTML\s*=/g, severity: 'medium', message: 'innerHTML can lead to XSS vulnerabilities' }, { pattern: /document\.write\s*\(/g, severity: 'medium', message: 'document.write() can be dangerous' }, { pattern: /\$\{.*\}/g, severity: 'low', message: 'Template literals with user input should be carefully reviewed' } ];
patterns.forEach(({ pattern, severity, message }) => { const matches = code.match(pattern); if (matches) { this.vulnerabilities.push({ severity, message, occurrences: matches.length, pattern: pattern.source }); } });
return this.vulnerabilities; }
generateReport() { const severityCounts = this.vulnerabilities.reduce((acc, vuln) => { acc[vuln.severity] = (acc[vuln.severity] || 0) + 1; return acc; }, {});
return { summary: severityCounts, details: this.vulnerabilities, riskScore: this.calculateRiskScore() }; }
calculateRiskScore() { const weights = { high: 10, medium: 5, low: 1 }; return this.vulnerabilities.reduce((score, vuln) => { return score + weights[vuln.severity]; }, 0); }}
// Usageconst auditor = new SecurityAuditor();const codeToAudit = ` function displayUserInput(input) { document.getElementById('content').innerHTML = input; eval('console.log("User input: " + input)'); }`;
const vulnerabilities = auditor.checkForInsecurePatterns(codeToAudit);const report = auditor.generateReport();console.log('Security Report:', report);
This comprehensive security guide provides essential practices for building secure JavaScript applications. Remember that security is an ongoing process that requires constant vigilance and updates.
Always stay updated with the latest security advisories and regularly audit your applications for vulnerabilities.