Security and Best Practices
1. Input Validation and Sanitization
Common Input Threats
| Threat | Description | Prevention |
|---|---|---|
| SQL Injection | Malicious SQL in input | Parameterized queries, ORM |
| XSS (Cross-Site Scripting) | Malicious scripts in content | Escape output, CSP headers |
| Command Injection | OS commands in input | Avoid shell execution, whitelist |
| Path Traversal | Access unauthorized files | Validate paths, use basedir |
| LDAP Injection | Malicious LDAP queries | Escape special chars, whitelist |
Validation Strategies
| Strategy | When to Use | Example |
|---|---|---|
| Whitelist | Known valid values | Enum values, file extensions |
| Blacklist | Last resort only | Block specific patterns |
| Type Checking | Enforce data types | typeof, instanceof, schema |
| Length Limits | Prevent overflow/DoS | Max string/array length |
| Format Validation | Structured data | Email, URL, phone regex |
Sanitization Techniques
| Technique | Purpose | Library |
|---|---|---|
| HTML Escaping | Prevent XSS | DOMPurify, he |
| URL Encoding | Safe URL parameters | encodeURIComponent() |
| SQL Escaping | Prevent SQL injection | ORM, prepared statements |
| Trim/Normalize | Consistent format | trim(), normalize() |
Example: Input validation and sanitization
// Email validation
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Strong password validation
function isStrongPassword(password) {
// At least 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char
const minLength = password.length >= 8;
const hasUpper = /[A-Z]/.test(password);
const hasLower = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return minLength && hasUpper && hasLower && hasNumber && hasSpecial;
}
// Comprehensive input validator
class InputValidator {
static validateString(value, options = {}) {
const {
minLength = 0,
maxLength = Infinity,
pattern = null,
allowEmpty = false
} = options;
if (!value && !allowEmpty) {
throw new Error('Value is required');
}
if (typeof value !== 'string') {
throw new Error('Value must be a string');
}
if (value.length < minLength) {
throw new Error(`Value must be at least ${minLength} characters`);
}
if (value.length > maxLength) {
throw new Error(`Value must not exceed ${maxLength} characters`);
}
if (pattern && !pattern.test(value)) {
throw new Error('Value does not match required pattern');
}
return true;
}
static validateNumber(value, options = {}) {
const {
min = -Infinity,
max = Infinity,
integer = false
} = options;
const num = Number(value);
if (isNaN(num)) {
throw new Error('Value must be a number');
}
if (integer && !Number.isInteger(num)) {
throw new Error('Value must be an integer');
}
if (num < min) {
throw new Error(`Value must be at least ${min}`);
}
if (num > max) {
throw new Error(`Value must not exceed ${max}`);
}
return true;
}
static validateEnum(value, allowedValues) {
if (!allowedValues.includes(value)) {
throw new Error(
`Value must be one of: ${allowedValues.join(', ')}`
);
}
return true;
}
static validateArray(value, options = {}) {
const {
minLength = 0,
maxLength = Infinity,
itemValidator = null
} = options;
if (!Array.isArray(value)) {
throw new Error('Value must be an array');
}
if (value.length < minLength) {
throw new Error(`Array must have at least ${minLength} items`);
}
if (value.length > maxLength) {
throw new Error(`Array must not exceed ${maxLength} items`);
}
if (itemValidator) {
value.forEach((item, index) => {
try {
itemValidator(item);
} catch (error) {
throw new Error(`Item at index ${index}: ${error.message}`);
}
});
}
return true;
}
}
// Usage examples
try {
InputValidator.validateString('hello', {minLength: 3, maxLength: 10});
InputValidator.validateNumber(25, {min: 0, max: 100});
InputValidator.validateEnum('active', ['active', 'inactive', 'pending']);
console.log('All validations passed');
} catch (error) {
console.error('Validation error:', error.message);
}
// HTML escaping to prevent XSS
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// Safe HTML rendering
function safeRender(userInput) {
const escaped = escapeHtml(userInput);
document.getElementById('output').textContent = escaped;
// or use textContent instead of innerHTML
}
// URL parameter sanitization
function sanitizeUrlParam(param) {
return encodeURIComponent(param);
}
// Build safe URL
function buildUrl(base, params) {
const queryString = Object.entries(params)
.map(([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
)
.join('&');
return `${base}?${queryString}`;
}
const url = buildUrl('https://api.example.com/search', {
q: 'user input <script>',
page: 1
});
// Result: https://api.example.com/search?q=user%20input%20%3Cscript%3E&page=1
// Filename sanitization (prevent path traversal)
function sanitizeFilename(filename) {
// Remove path separators and null bytes
return filename
.replace(/[\/\\]/g, '')
.replace(/\0/g, '')
.replace(/\.\./g, '')
.slice(0, 255); // Limit length
}
// Path traversal prevention
function safePath(userPath, baseDir) {
const path = require('path');
const resolvedPath = path.resolve(baseDir, userPath);
// Ensure resolved path is within baseDir
if (!resolvedPath.startsWith(path.resolve(baseDir))) {
throw new Error('Path traversal detected');
}
return resolvedPath;
}
// Schema validation with custom validator
const userSchema = {
username: {
type: 'string',
minLength: 3,
maxLength: 20,
pattern: /^[a-zA-Z0-9_]+$/
},
email: {
type: 'string',
validator: isValidEmail
},
age: {
type: 'number',
min: 18,
max: 120,
integer: true
},
role: {
type: 'enum',
values: ['user', 'admin', 'moderator']
}
};
function validateSchema(data, schema) {
const errors = [];
for (const [field, rules] of Object.entries(schema)) {
const value = data[field];
try {
if (rules.type === 'string') {
InputValidator.validateString(value, rules);
} else if (rules.type === 'number') {
InputValidator.validateNumber(value, rules);
} else if (rules.type === 'enum') {
InputValidator.validateEnum(value, rules.values);
}
if (rules.validator && !rules.validator(value)) {
errors.push(`${field}: Custom validation failed`);
}
} catch (error) {
errors.push(`${field}: ${error.message}`);
}
}
return {
valid: errors.length === 0,
errors
};
}
// Usage
const userData = {
username: 'john_doe',
email: 'john@example.com',
age: 25,
role: 'user'
};
const result = validateSchema(userData, userSchema);
if (result.valid) {
console.log('User data is valid');
} else {
console.error('Validation errors:', result.errors);
}
// SQL injection prevention (using parameterized queries)
// BAD: Vulnerable to SQL injection
function getUserBad(userId) {
const query = `SELECT * FROM users WHERE id = ${userId}`;
// If userId is "1 OR 1=1", returns all users!
return db.query(query);
}
// GOOD: Parameterized query
function getUserGood(userId) {
const query = 'SELECT * FROM users WHERE id = ?';
return db.query(query, [userId]);
}
// ORM approach (safer)
function getUserORM(userId) {
return User.findById(userId);
}
// Command injection prevention
// BAD: Vulnerable to command injection
function processBad(filename) {
const exec = require('child_process').exec;
exec(`cat ${filename}`, (error, stdout) => {
console.log(stdout);
});
// If filename is "file.txt; rm -rf /", disaster!
}
// GOOD: Use array syntax, avoid shell
function processGood(filename) {
const {execFile} = require('child_process');
execFile('cat', [filename], (error, stdout) => {
console.log(stdout);
});
}
// Rate limiting for API endpoints
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = new Map();
}
isAllowed(identifier) {
const now = Date.now();
const userRequests = this.requests.get(identifier) || [];
// Remove old requests outside window
const validRequests = userRequests.filter(
time => now - time < this.windowMs
);
if (validRequests.length >= this.maxRequests) {
return false;
}
validRequests.push(now);
this.requests.set(identifier, validRequests);
return true;
}
}
// Usage
const limiter = new RateLimiter(100, 60000); // 100 requests per minute
function handleRequest(req, res) {
const userId = req.userId;
if (!limiter.isAllowed(userId)) {
return res.status(429).json({error: 'Too many requests'});
}
// Process request
}
// Content Security Policy (CSP) nonce generation
function generateNonce() {
const crypto = require('crypto');
return crypto.randomBytes(16).toString('base64');
}
Key Points: Always validate input on server side (client-side
validation is for UX only). Use whitelist validation when possible. Escape HTML
to prevent XSS. Use parameterized queries to prevent SQL injection. Sanitize filenames to prevent path
traversal. Implement rate limiting to prevent DoS. Never trust user input. Validate type, length, format, and
range. Use established libraries for validation (Joi, Yup, validator.js).
2. XSS Prevention and Content Security Policy
XSS Attack Types
| Type | Description | Example |
|---|---|---|
| Reflected XSS | Script in URL/input reflected immediately | ?q=<script>alert(1)</script> |
| Stored XSS | Script stored in database, served to users | Comment with malicious script |
| DOM-based XSS | Client-side script manipulation | document.write(location.hash) |
| Mutation XSS | Browser mutates sanitized HTML | Nested tags, encoding tricks |
XSS Prevention Techniques
| Technique | How It Works | Implementation |
|---|---|---|
| Output Encoding | Escape HTML entities | < > & " |
| Use textContent | Automatically escapes content | element.textContent = data |
| Avoid innerHTML | Prevents script execution | Use DOM methods instead |
| CSP Headers | Restrict script sources | Content-Security-Policy header |
| DOMPurify | Sanitize HTML safely | Library for HTML cleaning |
CSP Directives
| Directive | Purpose | Example Value |
|---|---|---|
| default-src | Fallback for all sources | 'self' |
| script-src | JavaScript sources | 'self' 'nonce-xyz123' |
| style-src | CSS sources | 'self' 'unsafe-inline' |
| img-src | Image sources | 'self' data: https: |
| connect-src | XHR/WebSocket/EventSource | 'self' https://api.example.com |
| font-src | Font sources | 'self' https://fonts.gstatic.com |
| frame-ancestors | Who can embed in iframe | 'none' or 'self' |
Example: XSS prevention
// XSS vulnerable code examples
// BAD: innerHTML with user input
function displayUsernameBad(username) {
document.getElementById('greeting').innerHTML =
`<h1>Welcome, ${username}!</h1>`;
// If username is "<img src=x onerror=alert('XSS')>", XSS occurs!
}
// GOOD: Use textContent
function displayUsernameGood(username) {
const h1 = document.createElement('h1');
h1.textContent = `Welcome, ${username}!`;
document.getElementById('greeting').appendChild(h1);
}
// BAD: eval with user input
function executeBad(userCode) {
eval(userCode); // NEVER DO THIS!
}
// GOOD: Use safe alternatives
function executeGood(userCode) {
// If you must parse JSON
const data = JSON.parse(userCode);
// For calculations, use Function constructor with validation
// But still be very careful!
}
// BAD: Direct DOM manipulation from URL
function displayFromUrlBad() {
const params = new URLSearchParams(window.location.search);
document.getElementById('content').innerHTML = params.get('message');
}
// GOOD: Sanitize before display
function displayFromUrlGood() {
const params = new URLSearchParams(window.location.search);
const message = params.get('message');
document.getElementById('content').textContent = message;
}
// HTML escaping function
function escapeHtml2(unsafe) {
return unsafe
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// JavaScript escaping (for embedding in <script> tags)
function escapeJs(unsafe) {
return unsafe
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/</g, '\\x3c')
.replace(/>/g, '\\x3e');
}
// URL encoding
function escapeUrl(unsafe) {
return encodeURIComponent(unsafe);
}
// Context-aware escaping
function safeDomInsert(element, content, context) {
switch (context) {
case 'text':
element.textContent = content;
break;
case 'attribute':
element.setAttribute('data-value', escapeHtml2(content));
break;
case 'url':
element.href = escapeUrl(content);
break;
case 'html':
// Use DOMPurify if you must allow HTML
// element.innerHTML = DOMPurify.sanitize(content);
break;
default:
throw new Error('Invalid context');
}
}
// DOMPurify usage (safe HTML sanitization)
// import DOMPurify from 'dompurify';
function renderSafeHtml(userHtml) {
const clean = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
ALLOWED_ATTR: ['href']
});
document.getElementById('content').innerHTML = clean;
}
// CSP Implementation
// Setting CSP via HTTP header (server-side)
/*
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-xyz123' https://trusted.cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
*/
// Setting CSP via meta tag
const cspMeta = document.createElement('meta');
cspMeta.httpEquiv = 'Content-Security-Policy';
cspMeta.content = "default-src 'self'; script-src 'self' 'unsafe-inline'";
document.head.appendChild(cspMeta);
// Nonce-based CSP
// Server generates nonce
function generateCspNonce() {
const crypto = require('crypto');
return crypto.randomBytes(16).toString('base64');
}
// In Express.js middleware
function cspMiddleware(req, res, next) {
const nonce = generateCspNonce();
res.locals.nonce = nonce;
res.setHeader(
'Content-Security-Policy',
`script-src 'self' 'nonce-${nonce}'`
);
next();
}
// In HTML template
// <script nonce="<%= nonce %>">
// console.log('This script is allowed');
// </script>
// CSP violation reporting
const cspWithReporting = `
default-src 'self';
script-src 'self';
report-uri /csp-violation-report-endpoint;
report-to csp-endpoint
`;
// Report-To header
const reportTo = JSON.stringify({
group: 'csp-endpoint',
max_age: 10886400,
endpoints: [{url: 'https://example.com/csp-reports'}]
});
// Handle CSP violation reports
function handleCspReport(req, res) {
const report = req.body;
console.log('CSP Violation:', {
documentUri: report['document-uri'],
violatedDirective: report['violated-directive'],
blockedUri: report['blocked-uri'],
sourceFile: report['source-file'],
lineNumber: report['line-number']
});
res.status(204).send();
}
// Trusted Types API (modern XSS prevention)
if (window.trustedTypes && trustedTypes.createPolicy) {
const policy = trustedTypes.createPolicy('myPolicy', {
createHTML: (string) => {
// Sanitize string
return DOMPurify.sanitize(string);
},
createScriptURL: (string) => {
// Validate script URL
const url = new URL(string, window.location.href);
if (url.origin === window.location.origin) {
return string;
}
throw new TypeError('Invalid script URL');
}
});
// Usage with Trusted Types
const html = policy.createHTML('<b>Safe HTML</b>');
element.innerHTML = html; // No XSS!
}
// XSS protection headers
function setSecurityHeaders(res) {
// XSS Protection (legacy browsers)
res.setHeader('X-XSS-Protection', '1; mode=block');
// Prevent MIME sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// Frame options (clickjacking prevention)
res.setHeader('X-Frame-Options', 'DENY');
// Referrer policy
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
}
// Safe JSON embedding in HTML
function safeJsonEmbed(data) {
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
}
// In HTML:
// <script>
// const data = <%= safeJsonEmbed(userData) %>;
// </script>
// Safe event handler binding
// BAD: Inline event handlers
// <button onclick="userFunction()">Click</button>
// GOOD: addEventListener
const button = document.getElementById('myButton');
button.addEventListener('click', function(e) {
e.preventDefault();
// Safe handler
});
// Sanitize URLs to prevent javascript: protocol
function isSafeUrl(url) {
try {
const parsed = new URL(url, window.location.href);
return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
} catch {
return false;
}
}
function setSafeHref(element, url) {
if (isSafeUrl(url)) {
element.href = url;
} else {
console.error('Unsafe URL blocked:', url);
element.href = '#';
}
}
Key Points: Never use innerHTML with user input - use
textContent or DOM methods. Never use eval() or Function() with user input.
Implement CSP headers to restrict script sources. Use nonce or hash for inline scripts. DOMPurify for safe HTML
sanitization. Trusted Types API for modern browsers. Context-aware escaping (HTML, JavaScript, URL, CSS).
Validate URLs to prevent javascript: protocol. Report CSP violations for monitoring.
3. Secure Coding Practices and Code Review
OWASP Top 10 Web Vulnerabilities
| Vulnerability | Risk | Prevention |
|---|---|---|
| Broken Access Control | Unauthorized access to resources | Enforce authorization checks |
| Cryptographic Failures | Sensitive data exposure | Encrypt data at rest and transit |
| Injection | SQL/Command/LDAP injection | Parameterized queries, validation |
| Insecure Design | Missing security controls | Threat modeling, secure design |
| Security Misconfiguration | Default configs, verbose errors | Harden configs, disable defaults |
| Vulnerable Components | Outdated libraries with CVEs | Regular updates, dependency scanning |
| Authentication Failures | Weak auth, session management | Strong passwords, MFA, secure sessions |
| Data Integrity Failures | Insecure deserialization | Validate serialized data, use safe formats |
| Logging Failures | Insufficient monitoring | Comprehensive logging, alerting |
| SSRF | Server-side request forgery | Whitelist URLs, validate destinations |
Secure Coding Principles
| Principle | Description | Example |
|---|---|---|
| Defense in Depth | Multiple security layers | Input validation + CSP + WAF |
| Least Privilege | Minimum necessary permissions | Read-only DB user for queries |
| Fail Securely | Deny by default on errors | Default to unauthorized |
| Complete Mediation | Check every access | Validate auth on each request |
| Open Design | Security through design, not obscurity | Public algorithms, secret keys |
| Separation of Duties | Split critical operations | Approver ≠ Requester |
Code Review Security Checklist
| Category | What to Check | Red Flags |
|---|---|---|
| Input Validation | All inputs validated and sanitized | Direct use of user input |
| Authentication | Proper auth checks, session management | Missing auth, weak passwords |
| Authorization | Access control enforced | Missing permission checks |
| Cryptography | Strong algorithms, proper key management | Hardcoded secrets, weak ciphers |
| Error Handling | No sensitive info in errors | Stack traces to users |
| Logging | Security events logged | Passwords in logs |
| Dependencies | Up-to-date, no known vulnerabilities | Outdated libraries |
Example: Secure coding practices
// Secure configuration management
// BAD: Hardcoded secrets
const API_KEY = 'sk_live_abc123xyz'; // NEVER DO THIS!
const DB_PASSWORD = 'mypassword123';
// GOOD: Environment variables
const API_KEY2 = process.env.API_KEY;
const DB_PASSWORD2 = process.env.DB_PASSWORD;
// BETTER: Use a secrets manager
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
async function getSecret(secretName) {
const [version] = await client.accessSecretVersion({
name: secretName
});
return version.payload.data.toString();
}
// Error handling - don't leak sensitive info
// BAD: Verbose error messages
function handleErrorBad(error, res) {
res.status(500).json({
error: error.message,
stack: error.stack, // Exposes internal structure!
query: error.sql // Exposes SQL queries!
});
}
// GOOD: Generic error messages
function handleErrorGood(error, res) {
console.error('Internal error:', error); // Log for debugging
res.status(500).json({
error: 'An error occurred. Please try again later.',
requestId: generateRequestId() // For support tracking
});
}
// Secure session management
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
app.use(session({
store: new RedisStore({client: redisClient}),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: 'strict', // CSRF protection
maxAge: 3600000 // 1 hour
},
name: 'sessionId' // Don't use default name
}));
// Authorization middleware
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({error: 'Unauthorized'});
}
next();
}
function requireRole(role) {
return async (req, res, next) => {
const user = await User.findById(req.session.userId);
if (!user || user.role !== role) {
return res.status(403).json({error: 'Forbidden'});
}
next();
};
}
// Usage
app.get('/admin', requireAuth, requireRole('admin'), (req, res) => {
// Admin-only endpoint
});
// Secure file uploads
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: './uploads/',
filename: (req, file, cb) => {
// Generate safe filename
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB limit
},
fileFilter: (req, file, cb) => {
// Whitelist file types
const allowedTypes = /jpeg|jpg|png|pdf/;
const extname = allowedTypes.test(
path.extname(file.originalname).toLowerCase()
);
const mimetype = allowedTypes.test(file.mimetype);
if (extname && mimetype) {
cb(null, true);
} else {
cb(new Error('Invalid file type'));
}
}
});
// Resource access control
class ResourceAccessControl {
static canAccess(user, resource, action) {
// Check if user owns resource
if (resource.userId === user.id) {
return true;
}
// Check role-based permissions
const permissions = {
admin: ['read', 'write', 'delete'],
moderator: ['read', 'write'],
user: ['read']
};
const userPermissions = permissions[user.role] || [];
return userPermissions.includes(action);
}
}
// Usage in endpoint
app.delete('/posts/:id', requireAuth, async (req, res) => {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.session.userId);
if (!ResourceAccessControl.canAccess(user, post, 'delete')) {
return res.status(403).json({error: 'Forbidden'});
}
await post.delete();
res.json({success: true});
});
// Secure random number generation
// BAD: Predictable
const badRandom = Math.random();
// GOOD: Cryptographically secure
const crypto = require('crypto');
const goodRandom = crypto.randomBytes(32).toString('hex');
// Generate secure token
function generateSecureToken() {
return crypto.randomBytes(32).toString('base64url');
}
// Time-constant string comparison (prevent timing attacks)
// BAD: Vulnerable to timing attacks
function compareTokenBad(a, b) {
return a === b; // Early return leaks info
}
// GOOD: Constant-time comparison
function compareTokenGood(a, b) {
return crypto.timingSafeEqual(
Buffer.from(a),
Buffer.from(b)
);
}
// Dependency scanning
// package.json scripts
{
"scripts": {
"audit": "npm audit",
"audit:fix": "npm audit fix",
"audit:check": "npm audit --audit-level=high"
}
}
// Automated security checks in CI/CD
// .github/workflows/security.yml
/*
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run npm audit
run: npm audit --audit-level=moderate
- name: Run Snyk
run: npx snyk test
*/
// Secure API key storage
class ApiKeyManager {
constructor() {
this.keys = new Map();
}
async storeKey(userId, apiKey) {
const crypto = require('crypto');
// Hash the key (don't store plain text)
const hash = crypto
.createHash('sha256')
.update(apiKey)
.digest('hex');
await db.apiKeys.insert({
userId,
keyHash: hash,
createdAt: new Date()
});
// Return key once to user, then forget it
return apiKey;
}
async validateKey(apiKey) {
const hash = crypto
.createHash('sha256')
.update(apiKey)
.digest('hex');
const record = await db.apiKeys.findOne({keyHash: hash});
return !!record;
}
}
// Logging security events
class SecurityLogger {
static logAuthSuccess(userId, ip) {
logger.info('Auth success', {userId, ip, event: 'LOGIN'});
}
static logAuthFailure(username, ip, reason) {
logger.warn('Auth failure', {username, ip, reason, event: 'LOGIN_FAIL'});
}
static logAccessDenied(userId, resource, action) {
logger.warn('Access denied', {
userId,
resource,
action,
event: 'ACCESS_DENIED'
});
}
static logSensitiveOperation(userId, operation, details) {
logger.info('Sensitive operation', {
userId,
operation,
details,
event: 'SENSITIVE_OP'
});
}
}
// Never log sensitive data
// BAD
logger.info('User login', {password: userPassword}); // NEVER!
// GOOD
logger.info('User login', {userId: user.id, timestamp: new Date()});
// Secure headers middleware
function securityHeaders(req, res, next) {
// Prevent clickjacking
res.setHeader('X-Frame-Options', 'DENY');
// Prevent MIME sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// XSS protection
res.setHeader('X-XSS-Protection', '1; mode=block');
// HTTPS only
res.setHeader('Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload');
// Referrer policy
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions policy
res.setHeader('Permissions-Policy',
'geolocation=(), microphone=(), camera=()');
next();
}
app.use(securityHeaders);
Key Points: Never hardcode secrets - use environment variables
or secrets manager. Don't expose detailed errors to users. Implement proper authorization checks. Use secure
session management (httpOnly, secure, sameSite cookies). Whitelist file uploads by type and size. Use
cryptographically secure random for tokens. Constant-time comparison for sensitive strings. Run npm audit
regularly. Log security events without sensitive data. Apply security headers. Follow OWASP Top 10. Code review
for security issues. Defense in depth approach.
4. Data Encryption and Hashing
Encryption vs Hashing
| Feature | Encryption | Hashing |
|---|---|---|
| Reversible | Yes (with key) | No (one-way) |
| Purpose | Protect data confidentiality | Verify data integrity |
| Use Case | Store credit cards, encrypt messages | Store passwords, checksums |
| Output | Variable length ciphertext | Fixed length hash digest |
| Key Required | Yes | No (salt optional) |
Encryption Algorithms
| Algorithm | Type | Key Size | Use Case |
|---|---|---|---|
| AES | Symmetric | 128, 192, 256 bits | General purpose encryption |
| RSA | Asymmetric | 2048, 4096 bits | Key exchange, digital signatures |
| ChaCha20 | Symmetric stream | 256 bits | Mobile devices, TLS |
| ECC | Asymmetric | 256, 384 bits | Smaller keys, IoT devices |
Hashing Algorithms
| Algorithm | Output Size | Status | Use Case |
|---|---|---|---|
| bcrypt | 60 chars | ✓ Secure | Password hashing (recommended) |
| scrypt | Variable | ✓ Secure | Password hashing, key derivation |
| Argon2 | Variable | ✓ Secure (newest) | Password hashing (best) |
| SHA-256 | 256 bits | ✓ Secure | Checksums, NOT passwords |
| SHA-1 | 160 bits | ✗ Broken | Deprecated |
| MD5 | 128 bits | ✗ Broken | Never use |
Example: Encryption and hashing
const crypto = require('crypto');
// === PASSWORD HASHING ===
// BAD: Plain MD5 (broken, too fast)
function hashPasswordBad(password) {
return crypto.createHash('md5').update(password).digest('hex');
// Vulnerable to rainbow tables and brute force
}
// GOOD: bcrypt with salt
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 12; // Higher = more secure but slower
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
// Usage
async function registerUser(username, password) {
const passwordHash = await hashPassword(password);
await db.users.insert({
username,
passwordHash, // Store hash, never plain password
createdAt: new Date()
});
}
async function loginUser(username, password) {
const user = await db.users.findOne({username});
if (!user) {
return false;
}
const isValid = await verifyPassword(password, user.passwordHash);
return isValid ? user : null;
}
// === SYMMETRIC ENCRYPTION (AES) ===
// Encrypt data with AES-256-GCM
function encrypt(plaintext, key) {
// Generate random IV (initialization vector)
const iv = crypto.randomBytes(16);
// Create cipher
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
// Encrypt
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
// Get authentication tag
const authTag = cipher.getAuthTag();
// Return IV + authTag + encrypted data
return {
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
encrypted: encrypted
};
}
function decrypt(encryptedData, key) {
// Create decipher
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(encryptedData.iv, 'hex')
);
// Set authentication tag
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
// Decrypt
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Usage
const encryptionKey = crypto.randomBytes(32); // 256 bits
const message = 'Sensitive data';
const encrypted = encrypt(message, encryptionKey);
console.log('Encrypted:', encrypted);
const decrypted = decrypt(encrypted, encryptionKey);
console.log('Decrypted:', decrypted); // 'Sensitive data'
// === KEY DERIVATION ===
// Derive encryption key from password using PBKDF2
function deriveKey(password, salt) {
return crypto.pbkdf2Sync(
password,
salt,
100000, // iterations
32, // key length (256 bits)
'sha256' // hash algorithm
);
}
// Encrypt with password
function encryptWithPassword(plaintext, password) {
const salt = crypto.randomBytes(16);
const key = deriveKey(password, salt);
const encrypted = encrypt(plaintext, key);
return {
salt: salt.toString('hex'),
...encrypted
};
}
function decryptWithPassword(encryptedData, password) {
const salt = Buffer.from(encryptedData.salt, 'hex');
const key = deriveKey(password, salt);
return decrypt({
iv: encryptedData.iv,
authTag: encryptedData.authTag,
encrypted: encryptedData.encrypted
}, key);
}
// === ASYMMETRIC ENCRYPTION (RSA) ===
// Generate RSA key pair
function generateKeyPair() {
const {publicKey, privateKey} = crypto.generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: process.env.KEY_PASSPHRASE
}
});
return {publicKey, privateKey};
}
// Encrypt with public key
function rsaEncrypt(plaintext, publicKey) {
const buffer = Buffer.from(plaintext, 'utf8');
const encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString('base64');
}
// Decrypt with private key
function rsaDecrypt(encrypted, privateKey, passphrase) {
const buffer = Buffer.from(encrypted, 'base64');
const decrypted = crypto.privateDecrypt(
{
key: privateKey,
passphrase: passphrase
},
buffer
);
return decrypted.toString('utf8');
}
// Digital signature
function sign(message, privateKey, passphrase) {
const signer = crypto.createSign('sha256');
signer.update(message);
signer.end();
return signer.sign({
key: privateKey,
passphrase: passphrase
}, 'base64');
}
function verify(message, signature, publicKey) {
const verifier = crypto.createVerify('sha256');
verifier.update(message);
verifier.end();
return verifier.verify(publicKey, signature, 'base64');
}
// === HMAC (Hash-based Message Authentication Code) ===
function generateHmac(message, secret) {
return crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
}
function verifyHmac(message, hmac, secret) {
const calculated = generateHmac(message, secret);
return crypto.timingSafeEqual(
Buffer.from(hmac),
Buffer.from(calculated)
);
}
// Use for API request signing
function signRequest(payload, apiSecret) {
const timestamp = Date.now();
const message = `${timestamp}.${JSON.stringify(payload)}`;
const signature = generateHmac(message, apiSecret);
return {
timestamp,
payload,
signature
};
}
function verifyRequest(request, apiSecret, maxAge = 300000) {
const {timestamp, payload, signature} = request;
// Check timestamp (prevent replay attacks)
if (Date.now() - timestamp > maxAge) {
return false;
}
// Verify signature
const message = `${timestamp}.${JSON.stringify(payload)}`;
return verifyHmac(message, signature, apiSecret);
}
// === SECURE TOKEN GENERATION ===
function generateToken(length = 32) {
return crypto.randomBytes(length).toString('base64url');
}
// JWT-like token (simplified)
function createToken(payload, secret, expiresIn = 3600) {
const header = {alg: 'HS256', typ: 'JWT'};
const claims = {
...payload,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + expiresIn
};
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
const encodedPayload = Buffer.from(JSON.stringify(claims)).toString('base64url');
const signature = generateHmac(`${encodedHeader}.${encodedPayload}`, secret);
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
function verifyToken(token, secret) {
const [encodedHeader, encodedPayload, signature] = token.split('.');
// Verify signature
const expectedSignature = generateHmac(
`${encodedHeader}.${encodedPayload}`,
secret
);
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
throw new Error('Invalid signature');
}
// Decode and check expiration
const payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString());
if (payload.exp < Math.floor(Date.now() / 1000)) {
throw new Error('Token expired');
}
return payload;
}
// === ENCRYPTING DATA AT REST ===
class EncryptedStorage {
constructor(key) {
this.key = key;
}
set(key, value) {
const encrypted = encrypt(JSON.stringify(value), this.key);
localStorage.setItem(key, JSON.stringify(encrypted));
}
get(key) {
const stored = localStorage.getItem(key);
if (!stored) return null;
const encrypted = JSON.parse(stored);
const decrypted = decrypt(encrypted, this.key);
return JSON.parse(decrypted);
}
remove(key) {
localStorage.removeItem(key);
}
}
// Usage
const masterKey = deriveKey(userPassword, userSalt);
const storage = new EncryptedStorage(masterKey);
storage.set('sensitiveData', {ssn: '123-45-6789', account: '9876543210'});
const data = storage.get('sensitiveData');
// === WEB CRYPTO API (Browser) ===
async function generateAesKey() {
return await window.crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256
},
true, // extractable
['encrypt', 'decrypt']
);
}
async function encryptBrowser(plaintext, key) {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(plaintext);
const ciphertext = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
encoded
);
return {
iv: Array.from(iv),
ciphertext: Array.from(new Uint8Array(ciphertext))
};
}
async function decryptBrowser(encrypted, key) {
const decrypted = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(encrypted.iv)
},
key,
new Uint8Array(encrypted.ciphertext)
);
return new TextDecoder().decode(decrypted);
}
// === KEY MANAGEMENT BEST PRACTICES ===
// Store keys securely
// - Use environment variables
// - Use a secrets manager (AWS Secrets Manager, HashiCorp Vault)
// - Never commit keys to version control
// - Rotate keys regularly
// - Use different keys for different environments
// Key rotation example
class KeyRotation {
constructor() {
this.currentKey = null;
this.oldKeys = [];
}
rotateKey() {
if (this.currentKey) {
this.oldKeys.push(this.currentKey);
}
this.currentKey = crypto.randomBytes(32);
// Keep only last 3 old keys
if (this.oldKeys.length > 3) {
this.oldKeys.shift();
}
}
encrypt(data) {
return encrypt(data, this.currentKey);
}
decrypt(encryptedData) {
// Try current key first
try {
return decrypt(encryptedData, this.currentKey);
} catch (error) {
// Try old keys
for (const oldKey of this.oldKeys) {
try {
return decrypt(encryptedData, oldKey);
} catch {
continue;
}
}
throw new Error('Failed to decrypt with any key');
}
}
}
Key Points: Use bcrypt/Argon2 for password hashing, never plain
MD5/SHA. Use AES-256-GCM for symmetric encryption. Use RSA/ECC for asymmetric
encryption. Always use random IV for each encryption. HMAC for message authentication. Derive keys from
passwords
with PBKDF2/scrypt. Store keys securely (never hardcode). Rotate keys regularly. Use Web Crypto API in browsers.
Encryption protects confidentiality, hashing verifies integrity. Salt passwords to prevent rainbow tables.
5. Authentication and Authorization Patterns
Authentication Methods
| Method | How It Works | Use Case |
|---|---|---|
| Session-based | Server stores session, client has session ID | Traditional web apps |
| Token-based (JWT) | Stateless token with claims | APIs, SPAs, mobile apps |
| OAuth 2.0 | Delegated authorization | Third-party login |
| OpenID Connect | OAuth 2.0 + identity layer | SSO, enterprise auth |
| API Keys | Static key per client | Server-to-server |
| MFA/2FA | Multiple authentication factors | High security accounts |
Authorization Models
| Model | Description | Example |
|---|---|---|
| RBAC | Role-Based Access Control | Admin, User, Guest roles |
| ABAC | Attribute-Based Access Control | Rules based on attributes |
| ACL | Access Control List | Per-resource permissions |
| PBAC | Policy-Based Access Control | Complex business rules |
JWT Structure
| Part | Content | Purpose |
|---|---|---|
| Header | Algorithm, token type | Specify signing method |
| Payload | Claims (user ID, exp, etc.) | Carry user information |
| Signature | HMAC or RSA signature | Verify token integrity |
Example: Authentication and authorization
// === JWT AUTHENTICATION ===
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET;
// Generate JWT token
function generateJWT(user) {
const payload = {
userId: user.id,
email: user.email,
role: user.role
};
const token = jwt.sign(
payload,
JWT_SECRET,
{
expiresIn: '1h',
issuer: 'myapp.com',
audience: 'myapp-users'
}
);
return token;
}
// Verify JWT token
function verifyJWT(token) {
try {
const decoded = jwt.verify(token, JWT_SECRET, {
issuer: 'myapp.com',
audience: 'myapp-users'
});
return decoded;
} catch (error) {
if (error.name === 'TokenExpiredError') {
throw new Error('Token expired');
} else if (error.name === 'JsonWebTokenError') {
throw new Error('Invalid token');
}
throw error;
}
}
// Authentication middleware
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({error: 'No token provided'});
}
const token = authHeader.substring(7);
try {
const decoded = verifyJWT(token);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({error: error.message});
}
}
// === REFRESH TOKEN PATTERN ===
function generateTokenPair(user) {
const accessToken = jwt.sign(
{userId: user.id, email: user.email, role: user.role},
JWT_SECRET,
{expiresIn: '15m'} // Short-lived
);
const refreshToken = jwt.sign(
{userId: user.id, type: 'refresh'},
process.env.REFRESH_TOKEN_SECRET,
{expiresIn: '7d'} // Long-lived
);
return {accessToken, refreshToken};
}
async function refreshAccessToken(refreshToken) {
try {
const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
if (decoded.type !== 'refresh') {
throw new Error('Invalid token type');
}
// Check if refresh token is revoked
const isRevoked = await checkTokenRevocation(refreshToken);
if (isRevoked) {
throw new Error('Token revoked');
}
// Generate new access token
const user = await User.findById(decoded.userId);
const accessToken = generateJWT(user);
return accessToken;
} catch (error) {
throw new Error('Invalid refresh token');
}
}
// === ROLE-BASED ACCESS CONTROL (RBAC) ===
const ROLES = {
ADMIN: 'admin',
MODERATOR: 'moderator',
USER: 'user',
GUEST: 'guest'
};
const PERMISSIONS = {
READ_POSTS: 'read:posts',
WRITE_POSTS: 'write:posts',
DELETE_POSTS: 'delete:posts',
MANAGE_USERS: 'manage:users',
VIEW_ANALYTICS: 'view:analytics'
};
const ROLE_PERMISSIONS = {
[ROLES.ADMIN]: [
PERMISSIONS.READ_POSTS,
PERMISSIONS.WRITE_POSTS,
PERMISSIONS.DELETE_POSTS,
PERMISSIONS.MANAGE_USERS,
PERMISSIONS.VIEW_ANALYTICS
],
[ROLES.MODERATOR]: [
PERMISSIONS.READ_POSTS,
PERMISSIONS.WRITE_POSTS,
PERMISSIONS.DELETE_POSTS
],
[ROLES.USER]: [
PERMISSIONS.READ_POSTS,
PERMISSIONS.WRITE_POSTS
],
[ROLES.GUEST]: [
PERMISSIONS.READ_POSTS
]
};
function hasPermission(userRole, permission) {
const permissions = ROLE_PERMISSIONS[userRole] || [];
return permissions.includes(permission);
}
// Authorization middleware
function requirePermission(permission) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({error: 'Unauthorized'});
}
if (!hasPermission(req.user.role, permission)) {
return res.status(403).json({error: 'Forbidden'});
}
next();
};
}
// Usage
app.delete('/posts/:id',
authenticate,
requirePermission(PERMISSIONS.DELETE_POSTS),
async (req, res) => {
// Delete post
}
);
// === ATTRIBUTE-BASED ACCESS CONTROL (ABAC) ===
class AbacPolicy {
static canAccess(subject, resource, action, context) {
// Subject: who is trying to access (user)
// Resource: what is being accessed (document, post, etc.)
// Action: what operation (read, write, delete)
// Context: environmental factors (time, location, etc.)
// Rule 1: Users can read their own posts
if (action === 'read' && resource.authorId === subject.userId) {
return true;
}
// Rule 2: Admins can do anything
if (subject.role === 'admin') {
return true;
}
// Rule 3: Users can edit posts during business hours only
if (action === 'write' && resource.authorId === subject.userId) {
const hour = new Date().getHours();
return hour >= 9 && hour < 17; // 9 AM - 5 PM
}
// Rule 4: Moderators can delete flagged posts
if (action === 'delete' && subject.role === 'moderator' && resource.flagged) {
return true;
}
// Rule 5: Premium users can access premium content
if (resource.type === 'premium' && subject.subscription === 'premium') {
return true;
}
return false;
}
}
// Usage
app.put('/posts/:id', authenticate, async (req, res) => {
const post = await Post.findById(req.params.id);
const context = {timestamp: Date.now()};
if (!AbacPolicy.canAccess(req.user, post, 'write', context)) {
return res.status(403).json({error: 'Forbidden'});
}
// Update post
});
// === OAUTH 2.0 IMPLEMENTATION ===
// Authorization code flow
app.get('/auth/google', (req, res) => {
const params = new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
redirect_uri: 'http://localhost:3000/auth/google/callback',
response_type: 'code',
scope: 'openid email profile',
state: generateSecureToken() // CSRF protection
});
res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params}`);
});
app.get('/auth/google/callback', async (req, res) => {
const {code, state} = req.query;
// Verify state (CSRF protection)
if (!verifyState(state)) {
return res.status(400).json({error: 'Invalid state'});
}
// Exchange code for token
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
code,
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
redirect_uri: 'http://localhost:3000/auth/google/callback',
grant_type: 'authorization_code'
})
});
const tokens = await response.json();
// Get user info
const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: {Authorization: `Bearer ${tokens.access_token}`}
});
const googleUser = await userResponse.json();
// Create or update user in database
let user = await User.findOne({googleId: googleUser.id});
if (!user) {
user = await User.create({
googleId: googleUser.id,
email: googleUser.email,
name: googleUser.name,
avatar: googleUser.picture
});
}
// Generate JWT for our app
const appToken = generateJWT(user);
res.json({token: appToken});
});
// === MULTI-FACTOR AUTHENTICATION (MFA) ===
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
// Setup MFA
async function setupMFA(user) {
const secret = speakeasy.generateSecret({
name: `MyApp (${user.email})`,
issuer: 'MyApp'
});
// Generate QR code
const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
// Store secret in database (encrypted!)
await User.update(user.id, {
mfaSecret: encrypt(secret.base32, encryptionKey),
mfaEnabled: false // Not enabled until verified
});
return {
secret: secret.base32,
qrCode: qrCodeUrl
};
}
// Verify MFA token
function verifyMFA(token, secret) {
return speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
window: 2 // Allow 2 time steps before/after
});
}
// Login with MFA
async function loginWithMFA(email, password, mfaToken) {
// Verify password
const user = await User.findOne({email});
const passwordValid = await verifyPassword(password, user.passwordHash);
if (!passwordValid) {
throw new Error('Invalid credentials');
}
// If MFA enabled, verify token
if (user.mfaEnabled) {
const secret = decrypt(user.mfaSecret, encryptionKey);
const mfaValid = verifyMFA(mfaToken, secret);
if (!mfaValid) {
throw new Error('Invalid MFA token');
}
}
return generateJWT(user);
}
// === API KEY AUTHENTICATION ===
async function generateApiKey(userId) {
const apiKey = `sk_${generateSecureToken(32)}`;
const hashedKey = crypto.createHash('sha256').update(apiKey).digest('hex');
await db.apiKeys.insert({
userId,
keyHash: hashedKey,
createdAt: new Date(),
lastUsed: null
});
return apiKey; // Show once, then forget
}
async function authenticateApiKey(apiKey) {
const hashedKey = crypto.createHash('sha256').update(apiKey).digest('hex');
const record = await db.apiKeys.findOne({keyHash: hashedKey});
if (!record) {
return null;
}
// Update last used
await db.apiKeys.update(record.id, {lastUsed: new Date()});
return await User.findById(record.userId);
}
// API key middleware
async function authenticateApi(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({error: 'API key required'});
}
const user = await authenticateApiKey(apiKey);
if (!user) {
return res.status(401).json({error: 'Invalid API key'});
}
req.user = user;
next();
}
Key Points: JWT for stateless auth - include expiration and
issuer claims. Use refresh tokens for long-lived sessions. RBAC for simple
permission models. ABAC for complex rules. OAuth 2.0 for third-party login. MFA for high-security accounts. Hash
API keys before storage. Validate tokens on every request. Use short-lived access tokens (15 min) with refresh
tokens (7 days). Store refresh tokens securely, support revocation. Always use HTTPS for auth.
6. Security Headers and HTTPS Integration
Essential Security Headers
| Header | Purpose | Recommended Value |
|---|---|---|
| Strict-Transport-Security | Force HTTPS | max-age=31536000; includeSubDomains |
| Content-Security-Policy | Prevent XSS, injection | default-src 'self'; script-src 'self' |
| X-Frame-Options | Prevent clickjacking | DENY or SAMEORIGIN |
| X-Content-Type-Options | Prevent MIME sniffing | nosniff |
| Referrer-Policy | Control referrer info | strict-origin-when-cross-origin |
| Permissions-Policy | Control browser features | geolocation=(), camera=() |
| X-XSS-Protection | XSS filter (legacy) | 1; mode=block |
CORS Headers
| Header | Purpose | Example Value |
|---|---|---|
| Access-Control-Allow-Origin | Allowed origins | https://example.com |
| Access-Control-Allow-Methods | Allowed HTTP methods | GET, POST, PUT, DELETE |
| Access-Control-Allow-Headers | Allowed request headers | Content-Type, Authorization |
| Access-Control-Allow-Credentials | Allow cookies | true |
| Access-Control-Max-Age | Preflight cache time | 86400 (24 hours) |
HTTPS Best Practices
| Practice | Description | Benefit |
|---|---|---|
| TLS 1.3 | Use latest TLS version | Faster, more secure |
| Strong Ciphers | Disable weak ciphers | Prevent downgrade attacks |
| HSTS | Force HTTPS always | Prevent protocol downgrade |
| Certificate Pinning | Pin expected certificates | Prevent MITM with fake certs |
| OCSP Stapling | Include cert status | Faster cert validation |
Example: Security headers and HTTPS
// === SECURITY HEADERS MIDDLEWARE ===
// Using helmet (recommended)
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
frameguard: {
action: 'deny'
},
noSniff: true,
xssFilter: true,
referrerPolicy: {
policy: 'strict-origin-when-cross-origin'
},
permissionsPolicy: {
features: {
geolocation: ["'none'"],
microphone: ["'none'"],
camera: ["'none'"],
payment: ["'self'"]
}
}
}));
// Manual security headers
function securityHeaders2(req, res, next) {
// HTTPS enforcement
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
// XSS Protection
res.setHeader('X-XSS-Protection', '1; mode=block');
// Prevent clickjacking
res.setHeader('X-Frame-Options', 'DENY');
// Prevent MIME sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// Referrer policy
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Content Security Policy
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'"
);
// Permissions Policy
res.setHeader(
'Permissions-Policy',
'geolocation=(), microphone=(), camera=()'
);
next();
}
// === CORS CONFIGURATION ===
const cors = require('cors');
// Simple CORS
app.use(cors({
origin: 'https://example.com',
credentials: true
}));
// Advanced CORS with whitelist
const allowedOrigins = [
'https://example.com',
'https://app.example.com',
'http://localhost:3000'
];
app.use(cors({
origin: function(origin, callback) {
// Allow requests with no origin (mobile apps, curl, etc.)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
maxAge: 86400 // 24 hours
}));
// Manual CORS middleware
function corsMiddleware(req, res, next) {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
// Handle preflight
if (req.method === 'OPTIONS') {
return res.status(204).end();
}
next();
}
// === HTTPS ENFORCEMENT ===
// Redirect HTTP to HTTPS
function forceHttps(req, res, next) {
if (req.secure || req.headers['x-forwarded-proto'] === 'https') {
return next();
}
res.redirect(301, `https://${req.hostname}${req.url}`);
}
app.use(forceHttps);
// Create HTTPS server
const https = require('https');
const fs = require('fs');
const httpsOptions = {
key: fs.readFileSync('./certs/private-key.pem'),
cert: fs.readFileSync('./certs/certificate.pem'),
ca: fs.readFileSync('./certs/ca-bundle.pem'),
// TLS configuration
minVersion: 'TLSv1.3',
ciphers: [
'TLS_AES_128_GCM_SHA256',
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256'
].join(':'),
// Prefer server cipher order
honorCipherOrder: true,
// Enable OCSP stapling
// requestOCSP: true
};
https.createServer(httpsOptions, app).listen(443, () => {
console.log('HTTPS server running on port 443');
});
// === SUBRESOURCE INTEGRITY (SRI) ===
// Generate SRI hash
function generateSriHash(content) {
const hash = crypto.createHash('sha384').update(content).digest('base64');
return `sha384-${hash}`;
}
// Usage in HTML
// <script
// src="https://cdn.example.com/library.js"
// integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
// crossorigin="anonymous"
// ></script>
// === CERTIFICATE PINNING (Mobile/Native Apps) ===
// HTTP Public Key Pinning header (deprecated, use cert pinning in app)
// res.setHeader(
// 'Public-Key-Pins',
// 'pin-sha256="base64=="; max-age=5184000; includeSubDomains'
// );
// Certificate pinning in Node.js
const tls = require('tls');
const pinnedFingerprint = 'AA:BB:CC:DD:EE:FF:...';
function checkCertificate(socket) {
const cert = socket.getPeerCertificate();
const fingerprint = cert.fingerprint256;
if (fingerprint !== pinnedFingerprint) {
throw new Error('Certificate pinning failed');
}
}
// === RATE LIMITING WITH HEADERS ===
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false,
handler: (req, res) => {
res.status(429).json({
error: 'Too many requests',
retryAfter: req.rateLimit.resetTime
});
}
});
app.use('/api/', limiter);
// Custom rate limit headers
function rateLimitHeaders(req, res, next) {
res.setHeader('X-RateLimit-Limit', '100');
res.setHeader('X-RateLimit-Remaining', '95');
res.setHeader('X-RateLimit-Reset', Math.floor(Date.now() / 1000) + 900);
next();
}
// === SECURE COOKIES ===
// Set secure cookie
function setSecureCookie(res, name, value) {
res.cookie(name, value, {
httpOnly: true, // No JavaScript access
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 3600000, // 1 hour
domain: '.example.com',
path: '/',
signed: true // Sign cookie with secret
});
}
// === SECURITY.TXT FILE ===
// Serve security.txt for responsible disclosure
app.get('/.well-known/security.txt', (req, res) => {
res.type('text/plain');
res.send(`
Contact: security@example.com
Expires: 2026-12-31T23:59:59.000Z
Preferred-Languages: en
Canonical: https://example.com/.well-known/security.txt
Policy: https://example.com/security-policy
Acknowledgments: https://example.com/hall-of-fame
`.trim());
});
// === SECURITY MONITORING ===
// Log security events
function logSecurityEvent(type, details) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
type,
details,
level: 'security'
}));
}
// Monitor suspicious activity
function detectSuspiciousActivity(req) {
// Check for SQL injection patterns
const sqlInjectionPattern = /(\bunion\b|\bselect\b|\bdrop\b|\binsert\b)/i;
if (sqlInjectionPattern.test(req.url) ||
sqlInjectionPattern.test(JSON.stringify(req.body))) {
logSecurityEvent('SQL_INJECTION_ATTEMPT', {
ip: req.ip,
url: req.url,
body: req.body
});
}
// Check for XSS patterns
const xssPattern = /(<script|javascript:|onerror=|onload=)/i;
if (xssPattern.test(JSON.stringify(req.body))) {
logSecurityEvent('XSS_ATTEMPT', {
ip: req.ip,
url: req.url
});
}
// Check for path traversal
if (req.url.includes('../') || req.url.includes('..\\')) {
logSecurityEvent('PATH_TRAVERSAL_ATTEMPT', {
ip: req.ip,
url: req.url
});
}
}
app.use((req, res, next) => {
detectSuspiciousActivity(req);
next();
});
Key Points: Use helmet.js for comprehensive security headers.
HSTS forces HTTPS. CSP prevents XSS and injection. X-Frame-Options prevents clickjacking. Configure CORS
properly
- whitelist origins. Use TLS 1.3 with strong ciphers. Set secure cookie flags (httpOnly, secure, sameSite).
Implement rate limiting. Use SRI for CDN resources. Certificate pinning for critical apps. Monitor security
events. Serve security.txt for disclosure. HTTPS everywhere - no exceptions.
Section 25 Summary: Security and Best Practices
- Input Validation: Validate all input server-side, whitelist over blacklist, prevent SQL/command/XSS injection
- Sanitization: Escape HTML entities, use parameterized queries, sanitize filenames, implement rate limiting
- XSS Prevention: Never use innerHTML with user input, avoid eval(), implement CSP headers, use DOMPurify
- CSP Directives: default-src, script-src (use nonces), style-src, img-src, connect-src, frame-ancestors
- Secure Coding: Never hardcode secrets, follow OWASP Top 10, defense in depth, least privilege principle
- Code Review: Check auth/authz, validate crypto usage, prevent sensitive data in errors/logs
- Password Hashing: Use bcrypt/Argon2 with salt, never plain MD5/SHA, use key derivation (PBKDF2)
- Encryption: AES-256-GCM for symmetric, RSA for asymmetric, random IV per encryption, HMAC for auth
- Authentication: JWT for stateless auth, refresh tokens for sessions, OAuth 2.0 for third-party, MFA for high security
- Authorization: RBAC for simple models, ABAC for complex rules, validate permissions on every request
- Security Headers: HSTS forces HTTPS, CSP prevents XSS, X-Frame-Options prevents clickjacking, use helmet.js
- HTTPS: Use TLS 1.3, strong ciphers, secure cookies (httpOnly, secure, sameSite), CORS whitelist