Web Security Fundamentals: Comprehensive Protection Guide
Table of Contents
- Introduction to Web Security
- Common Web Vulnerabilities
- Authentication and Authorization
- Secure Communication (HTTPS/TLS)
- Input Validation and Sanitization
- Session Management
- Cross-Site Scripting (XSS) Prevention
- SQL Injection Prevention
- Cross-Site Request Forgery (CSRF) Protection
- Security Headers and Content Security Policy
- Secure Development Practices
- Security Testing and Monitoring
Introduction to Web Security {#introduction}
Web security is the practice of protecting websites, web applications, and web services from cyber threats. With the increasing sophistication of attacks and the growing reliance on web applications, security must be built into every layer of web development.
The Security Mindset
- Defense in Depth: Multiple layers of security controls
- Principle of Least Privilege: Minimal access rights
- Fail Securely: Secure defaults when systems fail
- Don't Trust User Input: Validate everything
- Security by Design: Build security from the ground up
OWASP Top 10 2021
The Open Web Application Security Project (OWASP) Top 10 represents the most critical web application security risks:
- A01:2021 – Broken Access Control
- A02:2021 – Cryptographic Failures
- A03:2021 – Injection
- A04:2021 – Insecure Design
- A05:2021 – Security Misconfiguration
- A06:2021 – Vulnerable and Outdated Components
- A07:2021 – Identification and Authentication Failures
- A08:2021 – Software and Data Integrity Failures
- A09:2021 – Security Logging and Monitoring Failures
- A10:2021 – Server-Side Request Forgery (SSRF)
Common Web Vulnerabilities {#vulnerabilities}
Injection Attacks
# SQL Injection Example (VULNERABLE)
def get_user_data(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
return execute_query(query)
# Attacker input: "1 OR 1=1" exposes all users
# SECURE: Parameterized queries
def get_user_data_secure(user_id):
query = "SELECT * FROM users WHERE id = %s"
return execute_query(query, (user_id,))
# NoSQL Injection Prevention
def find_user_secure(username):
# VULNERABLE
# db.users.find({"username": request.form["username"]})
# SECURE
if not isinstance(username, str):
raise ValueError("Username must be a string")
return db.users.find({"username": username})
Cross-Site Scripting (XSS)
// Reflected XSS Example (VULNERABLE)
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Search results for: ${query}</h1>`);
});
// Attacker URL: /search?q=<script>alert('XSS')</script>
// SECURE: HTML escaping
const escapeHtml = (unsafe) => {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q || '');
res.send(`<h1>Search results for: ${query}</h1>`);
});
// Stored XSS Prevention
function sanitizeComment(comment) {
// Use a library like DOMPurify
return DOMPurify.sanitize(comment, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
ALLOWED_ATTR: []
});
}
Cross-Site Request Forgery (CSRF)
// CSRF Protection with tokens
const csrf = require('csrf');
const tokens = new csrf();
// Generate CSRF token
app.use((req, res, next) => {
if (!req.session.csrfSecret) {
req.session.csrfSecret = tokens.secretSync();
}
res.locals.csrfToken = tokens.create(req.session.csrfSecret);
next();
});
// Validate CSRF token
function validateCSRF(req, res, next) {
const token = req.body.csrfToken || req.headers['x-csrf-token'];
if (!tokens.verify(req.session.csrfSecret, token)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
next();
}
// Protected route
app.post('/transfer-money', validateCSRF, (req, res) => {
// Process money transfer
});
// HTML form with CSRF token
`<form method="POST" action="/transfer-money">
<input type="hidden" name="csrfToken" value="${csrfToken}">
<input type="number" name="amount" required>
<button type="submit">Transfer</button>
</form>`
Authentication and Authorization {#auth}
Secure Password Handling
import bcrypt
import secrets
from datetime import datetime, timedelta
class PasswordManager:
@staticmethod
def hash_password(password: str) -> str:
"""Hash password using bcrypt."""
# Generate salt and hash password
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
return hashed.decode('utf-8')
@staticmethod
def verify_password(password: str, hashed: str) -> bool:
"""Verify password against hash."""
return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
@staticmethod
def generate_secure_token() -> str:
"""Generate cryptographically secure random token."""
return secrets.token_urlsafe(32)
# Password strength validation
def validate_password_strength(password: str) -> dict:
"""Validate password meets security requirements."""
errors = []
if len(password) < 12:
errors.append("Password must be at least 12 characters long")
if not re.search(r'[A-Z]', password):
errors.append("Password must contain uppercase letters")
if not re.search(r'[a-z]', password):
errors.append("Password must contain lowercase letters")
if not re.search(r'\d', password):
errors.append("Password must contain numbers")
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
errors.append("Password must contain special characters")
# Check against common passwords
if check_common_password(password):
errors.append("Password is too common")
return {
'valid': len(errors) == 0,
'errors': errors
}
# Account lockout protection
class AccountLockout:
def __init__(self, max_attempts=5, lockout_duration=900): # 15 minutes
self.max_attempts = max_attempts
self.lockout_duration = lockout_duration
self.failed_attempts = {}
def record_failed_attempt(self, username: str):
"""Record a failed login attempt."""
now = datetime.now()
if username not in self.failed_attempts:
self.failed_attempts[username] = []
self.failed_attempts[username].append(now)
# Clean old attempts
cutoff = now - timedelta(seconds=self.lockout_duration)
self.failed_attempts[username] = [
attempt for attempt in self.failed_attempts[username]
if attempt > cutoff
]
def is_locked(self, username: str) -> bool:
"""Check if account is locked."""
if username not in self.failed_attempts:
return False
recent_attempts = len(self.failed_attempts[username])
return recent_attempts >= self.max_attempts
def clear_attempts(self, username: str):
"""Clear failed attempts after successful login."""
if username in self.failed_attempts:
del self.failed_attempts[username]
Multi-Factor Authentication (MFA)
import pyotp
import qrcode
from io import BytesIO
class MFAManager:
@staticmethod
def generate_secret() -> str:
"""Generate TOTP secret for user."""
return pyotp.random_base32()
@staticmethod
def generate_qr_code(user_email: str, secret: str, issuer: str = "MyApp") -> bytes:
"""Generate QR code for TOTP setup."""
totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
name=user_email,
issuer_name=issuer
)
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(totp_uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img_buffer = BytesIO()
img.save(img_buffer, format='PNG')
return img_buffer.getvalue()
@staticmethod
def verify_totp(secret: str, token: str) -> bool:
"""Verify TOTP token."""
totp = pyotp.TOTP(secret)
return totp.verify(token, valid_window=1) # Allow 30-second window
# Usage in Flask app
@app.route('/setup-mfa', methods=['POST'])
@login_required
def setup_mfa():
user = current_user
if not user.mfa_secret:
user.mfa_secret = MFAManager.generate_secret()
db.session.commit()
qr_code = MFAManager.generate_qr_code(user.email, user.mfa_secret)
return send_file(BytesIO(qr_code), mimetype='image/png')
@app.route('/verify-mfa', methods=['POST'])
@login_required
def verify_mfa():
token = request.json.get('token')
user = current_user
if MFAManager.verify_totp(user.mfa_secret, token):
user.mfa_enabled = True
db.session.commit()
return jsonify({'success': True})
return jsonify({'error': 'Invalid token'}), 400
JWT Security
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class JWTManager {
constructor() {
// Use strong, randomly generated secrets
this.accessTokenSecret = process.env.JWT_ACCESS_SECRET;
this.refreshTokenSecret = process.env.JWT_REFRESH_SECRET;
if (!this.accessTokenSecret || !this.refreshTokenSecret) {
throw new Error('JWT secrets must be set in environment variables');
}
}
generateTokens(user) {
const payload = {
userId: user.id,
email: user.email,
roles: user.roles
};
// Short-lived access token (15 minutes)
const accessToken = jwt.sign(payload, this.accessTokenSecret, {
expiresIn: '15m',
issuer: 'myapp.com',
audience: 'myapp-users'
});
// Longer-lived refresh token (7 days)
const refreshToken = jwt.sign(
{ userId: user.id, tokenId: crypto.randomUUID() },
this.refreshTokenSecret,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
verifyAccessToken(token) {
try {
return jwt.verify(token, this.accessTokenSecret, {
issuer: 'myapp.com',
audience: 'myapp-users'
});
} catch (error) {
throw new Error('Invalid access token');
}
}
verifyRefreshToken(token) {
try {
return jwt.verify(token, this.refreshTokenSecret);
} catch (error) {
throw new Error('Invalid refresh token');
}
}
}
// Middleware for token validation
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = jwtManager.verifyAccessToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
}
Secure Communication (HTTPS/TLS) {#https}
TLS Configuration
# Nginx HTTPS configuration
server {
listen 443 ssl http2;
server_name example.com;
# SSL certificate files
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Perfect Forward Secrecy
ssl_dhparam /path/to/dhparam.pem;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/chain.crt;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Certificate Management
# Generate Let's Encrypt certificate
certbot --nginx -d example.com -d www.example.com
# Auto-renewal setup
echo "0 0,12 * * * root certbot renew --quiet" | sudo tee -a /etc/crontab
# Custom certificate generation (for development)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
Input Validation and Sanitization {#validation}
Server-Side Validation
const validator = require('validator');
const xss = require('xss');
class InputValidator {
static validateEmail(email) {
if (!email || typeof email !== 'string') {
throw new Error('Email is required and must be a string');
}
if (!validator.isEmail(email)) {
throw new Error('Invalid email format');
}
if (email.length > 254) {
throw new Error('Email too long');
}
return validator.normalizeEmail(email);
}
static validatePassword(password) {
if (!password || typeof password !== 'string') {
throw new Error('Password is required and must be a string');
}
if (password.length < 12 || password.length > 128) {
throw new Error('Password must be between 12 and 128 characters');
}
const hasUpper = /[A-Z]/.test(password);
const hasLower = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
if (!hasUpper || !hasLower || !hasNumber || !hasSpecial) {
throw new Error('Password must contain uppercase, lowercase, number, and special character');
}
return password;
}
static sanitizeHTML(input) {
if (typeof input !== 'string') return '';
return xss(input, {
whiteList: {
p: [],
br: [],
strong: [],
em: [],
b: [],
i: []
},
stripIgnoreTag: true,
stripIgnoreTagBody: ['script']
});
}
static validateInteger(input, min = null, max = null) {
const num = parseInt(input, 10);
if (isNaN(num)) {
throw new Error('Invalid number format');
}
if (min !== null && num < min) {
throw new Error(`Number must be at least ${min}`);
}
if (max !== null && num > max) {
throw new Error(`Number must be at most ${max}`);
}
return num;
}
}
// Express middleware for input validation
function validateUserInput(req, res, next) {
try {
if (req.body.email) {
req.body.email = InputValidator.validateEmail(req.body.email);
}
if (req.body.password) {
req.body.password = InputValidator.validatePassword(req.body.password);
}
if (req.body.comment) {
req.body.comment = InputValidator.sanitizeHTML(req.body.comment);
}
next();
} catch (error) {
res.status(400).json({ error: error.message });
}
}
File Upload Security
const multer = require('multer');
const path = require('path');
const crypto = require('crypto');
// Secure file upload configuration
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
// Generate secure filename
const uniqueSuffix = crypto.randomBytes(16).toString('hex');
const ext = path.extname(file.originalname);
cb(null, `${uniqueSuffix}${ext}`);
}
});
const fileFilter = (req, file, cb) => {
// Whitelist allowed file types
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'), false);
}
};
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
files: 5 // Maximum 5 files
},
fileFilter: fileFilter
});
// Additional file validation
function validateUploadedFile(req, res, next) {
if (!req.file) {
return next();
}
const file = req.file;
// Verify file extension matches MIME type
const allowedExtensions = {
'image/jpeg': ['.jpg', '.jpeg'],
'image/png': ['.png'],
'image/gif': ['.gif'],
'application/pdf': ['.pdf']
};
const ext = path.extname(file.originalname).toLowerCase();
const allowedExts = allowedExtensions[file.mimetype];
if (!allowedExts || !allowedExts.includes(ext)) {
return res.status(400).json({ error: 'File extension does not match MIME type' });
}
next();
}
Session Management {#sessions}
Secure Session Configuration
const session = require('express-session');
const MongoStore = require('connect-mongo');
const crypto = require('crypto');
// Secure session configuration
app.use(session({
secret: process.env.SESSION_SECRET, // Strong, random secret
name: 'sessionId', // Change default session name
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only in production
httpOnly: true, // Prevent XSS
maxAge: 1000 * 60 * 60 * 24, // 24 hours
sameSite: 'strict' // CSRF protection
},
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
touchAfter: 24 * 3600 // Lazy session update
})
}));
// Session security middleware
function sessionSecurity(req, res, next) {
// Regenerate session ID on login
if (req.body.action === 'login' && req.user) {
req.session.regenerate((err) => {
if (err) {
return next(err);
}
req.session.userId = req.user.id;
next();
});
} else {
next();
}
}
// Session timeout handling
function checkSessionTimeout(req, res, next) {
if (req.session && req.session.lastActivity) {
const timeout = 30 * 60 * 1000; // 30 minutes
const now = Date.now();
if (now - req.session.lastActivity > timeout) {
req.session.destroy((err) => {
if (err) console.error('Session destruction error:', err);
return res.status(401).json({ error: 'Session expired' });
});
return;
}
}
if (req.session) {
req.session.lastActivity = Date.now();
}
next();
}
Security Headers and Content Security Policy {#headers}
Comprehensive Security Headers
const helmet = require('helmet');
// Helmet configuration for security headers
app.use(helmet({
// Content Security Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
scriptSrc: ["'self'", "https://cdnjs.cloudflare.com"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
mediaSrc: ["'self'"],
objectSrc: ["'none'"],
childSrc: ["'none'"],
workerSrc: ["'self'"],
frameSrc: ["'none'"],
formAction: ["'self'"],
upgradeInsecureRequests: []
},
reportOnly: false
},
// HTTP Strict Transport Security
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true
},
// X-Frame-Options
frameguard: {
action: 'deny'
},
// X-Content-Type-Options
noSniff: true,
// X-XSS-Protection
xssFilter: true,
// Referrer Policy
referrerPolicy: {
policy: 'strict-origin-when-cross-origin'
},
// Permissions Policy
permissionsPolicy: {
features: {
geolocation: ["'none'"],
microphone: ["'none'"],
camera: ["'none'"],
payment: ["'none'"],
usb: ["'none'"]
}
}
}));
// Custom security headers
app.use((req, res, next) => {
// Feature Policy (deprecated but still used by some browsers)
res.setHeader('Feature-Policy', "geolocation 'none'; microphone 'none'; camera 'none'");
// Server header removal
res.removeHeader('X-Powered-By');
// Custom security header
res.setHeader('X-Custom-Security', 'enabled');
next();
});
Content Security Policy Implementation
<!-- Inline CSP (fallback) -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'nonce-{{nonce}}' https://cdnjs.cloudflare.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
media-src 'self';
object-src 'none';
child-src 'none';
worker-src 'self';
frame-src 'none';
form-action 'self';
upgrade-insecure-requests;
">
<!-- Nonce-based script execution -->
<script nonce="{{nonce}}">
// Safe inline script with nonce
console.log('This script is allowed');
</script>
Secure Development Practices {#practices}
Security Code Review Checklist
## Security Code Review Checklist
### Authentication & Authorization
- [ ] Password hashing using bcrypt/scrypt/Argon2
- [ ] Strong password requirements enforced
- [ ] Account lockout mechanisms implemented
- [ ] Multi-factor authentication available
- [ ] Proper session management
- [ ] JWT tokens properly validated
- [ ] Authorization checks on all endpoints
- [ ] Principle of least privilege applied
### Input Validation
- [ ] All user inputs validated server-side
- [ ] Parameterized queries used (no SQL injection)
- [ ] HTML output properly escaped
- [ ] File uploads validated and restricted
- [ ] URL parameters validated
- [ ] JSON input validated against schema
### Data Protection
- [ ] Sensitive data encrypted at rest
- [ ] TLS/HTTPS enforced for data in transit
- [ ] Secrets stored securely (not in code)
- [ ] Database credentials protected
- [ ] API keys managed securely
- [ ] Personal data handling complies with GDPR/CCPA
### Error Handling
- [ ] Generic error messages for user-facing errors
- [ ] Detailed errors logged securely
- [ ] No sensitive information in error messages
- [ ] Proper exception handling implemented
- [ ] Security events logged and monitored
### Dependencies
- [ ] Dependencies regularly updated
- [ ] Vulnerability scanning performed
- [ ] No known vulnerable packages used
- [ ] Dependency integrity verified
Environment Configuration
# .env file for development (never commit to repo)
NODE_ENV=development
DATABASE_URL=postgresql://user:password@localhost:5432/myapp_dev
SESSION_SECRET=your-super-secret-session-key-here
JWT_ACCESS_SECRET=your-jwt-access-secret-here
JWT_REFRESH_SECRET=your-jwt-refresh-secret-here
ENCRYPTION_KEY=your-32-character-encryption-key-here
# Production environment variables (set in deployment platform)
NODE_ENV=production
DATABASE_URL=${DATABASE_URL}
SESSION_SECRET=${SESSION_SECRET}
JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}
JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
ENCRYPTION_KEY=${ENCRYPTION_KEY}
Secrets Management
// Using Azure Key Vault
const { DefaultAzureCredential } = require('@azure/identity');
const { SecretClient } = require('@azure/keyvault-secrets');
class SecretsManager {
constructor() {
const credential = new DefaultAzureCredential();
this.client = new SecretClient(
process.env.AZURE_KEYVAULT_URL,
credential
);
}
async getSecret(secretName) {
try {
const secret = await this.client.getSecret(secretName);
return secret.value;
} catch (error) {
console.error(`Failed to retrieve secret ${secretName}:`, error);
throw new Error('Failed to retrieve secret');
}
}
}
// Usage
const secretsManager = new SecretsManager();
const dbPassword = await secretsManager.getSecret('database-password');
Security Testing and Monitoring {#testing}
Automated Security Testing
// Security testing with Jest and Supertest
const request = require('supertest');
const app = require('../app');
describe('Security Tests', () => {
test('should prevent SQL injection', async () => {
const maliciousInput = "'; DROP TABLE users; --";
const response = await request(app)
.get(`/api/users/${maliciousInput}`)
.expect(400);
expect(response.body.error).toContain('Invalid input');
});
test('should prevent XSS in comments', async () => {
const xssPayload = '<script>alert("XSS")</script>';
const response = await request(app)
.post('/api/comments')
.send({ content: xssPayload })
.expect(201);
expect(response.body.content).not.toContain('<script>');
expect(response.body.content).toContain('<script>');
});
test('should require CSRF token for state-changing operations', async () => {
await request(app)
.post('/api/transfer-money')
.send({ amount: 1000, to: 'attacker' })
.expect(403);
});
test('should enforce rate limiting', async () => {
const promises = Array(100).fill().map(() =>
request(app).post('/api/login').send({ email: 'test@test.com', password: 'wrong' })
);
const responses = await Promise.all(promises);
const tooManyRequests = responses.filter(r => r.status === 429);
expect(tooManyRequests.length).toBeGreaterThan(0);
});
});
Security Monitoring
const winston = require('winston');
// Security event logger
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'security.log' }),
new winston.transports.Console()
]
});
// Security middleware for monitoring
function securityMonitoring(req, res, next) {
// Log suspicious activities
const suspiciousPatterns = [
/(union|select|insert|delete|drop|exec|script)/i,
/<script|javascript:|vbscript:|onload|onerror/i,
/\.\.\//,
/\/etc\/passwd|\/proc\/|\/sys\//
];
const userInput = JSON.stringify({
url: req.url,
body: req.body,
query: req.query,
headers: req.headers
});
suspiciousPatterns.forEach(pattern => {
if (pattern.test(userInput)) {
securityLogger.warn('Suspicious activity detected', {
pattern: pattern.toString(),
ip: req.ip,
userAgent: req.get('User-Agent'),
url: req.url,
input: userInput
});
}
});
// Log failed authentication attempts
if (req.url.includes('/login') && res.statusCode === 401) {
securityLogger.warn('Failed login attempt', {
ip: req.ip,
userAgent: req.get('User-Agent'),
email: req.body.email
});
}
next();
}
app.use(securityMonitoring);
This comprehensive web security guide covers the fundamental aspects of securing web applications. Security is an ongoing process that requires constant vigilance, regular updates, and continuous learning about new threats and vulnerabilities. Always stay informed about the latest security best practices and update your applications accordingly.
Remember: Security is not a feature you add at the end – it must be built into every aspect of your application from the ground up.