CSRF protection in Node.js is essential for web application security. Learn how to implement anti-CSRF tokens in Express to keep your users safe today.
During an on-call rotation last year, I spent an entire afternoon debugging a session-related anomaly that turned out to be a classic CSRF oversight. We had secured our authentication flows, but we hadn't accounted for the fact that a browser automatically attaches cookies to cross-site requests. Implementing CSRF protection is one of the most critical steps in hardening a Node.js application, yet it’s often relegated to an afterthought.
Cross-Site Request Forgery (CSRF) exploits the trust a web application has in a user's browser. If a user is authenticated, the browser sends session cookies with every request to that domain—even if the request originated from a malicious third-party site. If your app doesn't verify the intent of the request, an attacker can perform actions on behalf of the user, like changing a password or transferring funds, without the user ever knowing.
When you're building with Express, you need to ensure that every state-changing request (POST, PUT, DELETE) is intentional. While you might be tempted to just rely on SameSite cookie attributes, they aren't a silver bullet. Browsers have improved, but defense-in-depth is non-negotiable in modern web application security.
The industry standard for CSRF protection in Node.js is the csurf middleware, or more recently, the csurf package's successor patterns using double-csrf or csurf alternatives. For this example, let's look at the implementation using a standard token-based approach.
First, install the necessary dependencies:
Bashnpm install csurf cookie-parser
In your app.js or server.js, you need to configure the middleware. It's vital that you use a cookie-parser and a session manager, as the token needs to be associated with the user's session.
JAVASCRIPTconst express = require(CE9178">'express'); const cookieParser = require(CE9178">'cookie-parser'); const csrf = require(CE9178">'csurf'); const app = express(); app.use(cookieParser()); app.use(express.urlencoded({ extended: false })); // Setup CSRF middleware const csrfProtection = csrf({ cookie: true }); app.get(CE9178">'/form', csrfProtection, (req, res) => { res.render(CE9178">'form', { csrfToken: req.csrfToken() }); }); app.post(CE9178">'/process', csrfProtection, (req, res) => { res.send(CE9178">'Data processed safely!'); });
Early in my career, I tried to implement a custom "secret header" check. It broke almost immediately because I hadn't accounted for the complexity of frontend frameworks and CORS policies. I eventually learned that using established libraries for anti-CSRF tokens is safer because they handle edge cases like token rotation and cryptographically strong random generation that are easy to get wrong when writing from scratch.
When you move toward building robust systems, you must also be aware of other common vulnerabilities. For instance, just as you manage CSRF, you should also focus on preventing SQL injection in modern frameworks to ensure your database isn't a secondary point of failure.
It’s helpful to see how these defenses stack up. CSRF protection is just one layer of your Express security best practices.
| Method | Protection Level | Implementation Effort |
|---|---|---|
| SameSite Cookies | Moderate | Low |
| Anti-CSRF Tokens | High | Medium |
| Custom Header Check | Low | Medium |
| Origin/Referer Check | Moderate | Low |
If you're looking to lock down your application, don't stop at tokens. Ensure you are also preventing session fixation by regenerating session IDs after login.
I’ve found that the biggest mistake developers make is applying CSRF protection only to a subset of routes. If you have an API that accepts state-changing requests, it needs protection regardless of whether it's an HTML form or a JSON fetch call. If you're building an API, consider using a custom header like X-Requested-With or X-CSRF-Token which you validate on the server side.
Q: Do I need CSRF protection for GET requests? A: No. According to HTTP standards, GET requests should be idempotent—they should only retrieve data, not change it. If your GET requests are performing actions, you have a design flaw that goes beyond CSRF.
Q: Can I use JWTs to prevent CSRF?
A: If you store your JWT in localStorage rather than a cookie, you are effectively immune to traditional CSRF because the browser won't automatically attach the token to requests. However, this introduces XSS risks, so choose your storage strategy carefully.
Q: What if I'm using a third-party payment gateway? A: Payment gateways usually handle their own CSRF protection via callbacks or webhooks. Always verify the signature of the incoming webhook to ensure it actually originated from the provider.
I’m still experimenting with moving away from server-side rendered tokens toward stateless CSRF validation for highly distributed architectures. It’s a trade-off between complexity and performance, and I’m not yet convinced that the added overhead is worth it for standard CRUD apps. Stick to the tried-and-true middleware approach until you hit a specific performance bottleneck.
WebSocket security starts with preventing CSWSH. Learn how to validate origins and secure your real-time connections in Node.js and Laravel applications.
Read moreServer-Side Template Injection (SSTI) can lead to full remote code execution. Learn how to secure your Node.js and PHP apps with these practical techniques.