Chapter 17: Security Best Practices
· 10 min read
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! 🎉