/** * CSRF Protection Middleware (Modern Approach) * * Uses SameSite cookies + double-submit cookie pattern * Replaces deprecated csurf package * * Reference: OWASP CSRF Prevention Cheat Sheet * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html */ const crypto = require('crypto'); const { logSecurityEvent, getClientIp } = require('../utils/security-logger'); /** * Generate CSRF token */ function generateCsrfToken() { return crypto.randomBytes(32).toString('hex'); } /** * CSRF Protection Middleware * * Uses double-submit cookie pattern: * 1. Server sets CSRF token in secure, SameSite cookie * 2. Client must send same token in custom header (X-CSRF-Token) * 3. Server validates cookie matches header */ function csrfProtection(req, res, next) { // Skip GET, HEAD, OPTIONS (safe methods) if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) { return next(); } // Get CSRF token from cookie const cookieToken = req.cookies['csrf-token']; // Get CSRF token from header const headerToken = req.headers['x-csrf-token'] || req.headers['csrf-token']; // Validate tokens exist and match if (!cookieToken || !headerToken || cookieToken !== headerToken) { logSecurityEvent({ type: 'csrf_violation', sourceIp: getClientIp(req), userId: req.user?.id, endpoint: req.path, userAgent: req.get('user-agent'), details: { method: req.method, hasCookie: !!cookieToken, hasHeader: !!headerToken, tokensMatch: cookieToken === headerToken }, action: 'blocked', severity: 'high' }); return res.status(403).json({ error: 'Forbidden', message: 'Invalid CSRF token', code: 'CSRF_VALIDATION_FAILED' }); } next(); } /** * Middleware to set CSRF token cookie * Apply this globally or on routes that need CSRF protection */ function setCsrfToken(req, res, next) { // Only set cookie if it doesn't exist if (!req.cookies['csrf-token']) { const token = generateCsrfToken(); //Check if we're behind a proxy (X-Forwarded-Proto header) const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https'; res.cookie('csrf-token', token, { httpOnly: true, secure: isSecure && process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 24 * 60 * 60 * 1000 // 24 hours }); } next(); } /** * Endpoint to get CSRF token for client-side usage * GET /api/csrf-token * * Returns the CSRF token from the cookie so client can include it in requests */ function getCsrfToken(req, res) { const token = req.cookies['csrf-token']; if (!token) { return res.status(400).json({ error: 'Bad Request', message: 'No CSRF token found. Visit the site first to receive a token.' }); } res.json({ csrfToken: token }); } module.exports = { csrfProtection, setCsrfToken, getCsrfToken, generateCsrfToken };