Web Security Fundamentals

Web Security Fundamentals: Comprehensive Protection Guide

Table of Contents

  1. Introduction to Web Security
  2. Common Web Vulnerabilities
  3. Authentication and Authorization
  4. Secure Communication (HTTPS/TLS)
  5. Input Validation and Sanitization
  6. Session Management
  7. Cross-Site Scripting (XSS) Prevention
  8. SQL Injection Prevention
  9. Cross-Site Request Forgery (CSRF) Protection
  10. Security Headers and Content Security Policy
  11. Secure Development Practices
  12. 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:

  1. A01:2021 – Broken Access Control
  2. A02:2021 – Cryptographic Failures
  3. A03:2021 – Injection
  4. A04:2021 – Insecure Design
  5. A05:2021 – Security Misconfiguration
  6. A06:2021 – Vulnerable and Outdated Components
  7. A07:2021 – Identification and Authentication Failures
  8. A08:2021 – Software and Data Integrity Failures
  9. A09:2021 – Security Logging and Monitoring Failures
  10. 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, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
};

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('&lt;script&gt;');
    });

    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.