From cfc4347e9bfc3cf1b46dc71790d0a4a955d73418 Mon Sep 17 00:00:00 2001 From: TheFlow Date: Fri, 24 Oct 2025 16:42:56 +1300 Subject: [PATCH] fix(csrf): enable newsletter subscription from mobile CRITICAL FIX: Newsletter subscription was returning "Forbidden" error because the CSRF protection was incorrectly configured. Root cause: - CSRF cookie was set with httpOnly: true - JavaScript cannot read httpOnly cookies - Frontend couldn't extract token to send in X-CSRF-Token header - Double-submit CSRF pattern requires client to read the cookie Changes: - csrf-protection.middleware.js: Set httpOnly: false (required for double-submit pattern) - blog.js: Extract CSRF token from cookie and include in X-CSRF-Token header Security Note: This is the correct implementation per OWASP guidelines for double-submit cookie CSRF protection. The cookie is still protected by SameSite: strict and domain restrictions. Fixes: #newsletter-subscription-forbidden-mobile --- public/js/blog.js | 9 ++++++++- src/middleware/csrf-protection.middleware.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/public/js/blog.js b/public/js/blog.js index ca82d788..ad3e830c 100644 --- a/public/js/blog.js +++ b/public/js/blog.js @@ -571,10 +571,17 @@ function setupNewsletterModal() { submitBtn.textContent = 'Subscribing...'; try { + // Get CSRF token from cookie + const csrfToken = document.cookie + .split('; ') + .find(row => row.startsWith('csrf-token=')) + ?.split('=')[1]; + const response = await fetch('/api/newsletter/subscribe', { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'X-CSRF-Token': csrfToken || '' }, body: JSON.stringify({ email, diff --git a/src/middleware/csrf-protection.middleware.js b/src/middleware/csrf-protection.middleware.js index 4da885a1..2a9a9e12 100644 --- a/src/middleware/csrf-protection.middleware.js +++ b/src/middleware/csrf-protection.middleware.js @@ -79,7 +79,7 @@ function setCsrfToken(req, res, next) { const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https'; res.cookie('csrf-token', token, { - httpOnly: true, + httpOnly: false, // Must be false for double-submit pattern (client needs to read it) secure: isSecure && process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 24 * 60 * 60 * 1000 // 24 hours