Cross-Site Request Forgery (CSRF) is an attack that tricks an authenticated user into submitting an unwanted request to a web application. To protect a Node.js application from this type of attack, we can implement a CSRF token system from scratch. This article shows step by step how to do it.
1. Generate a CSRF Token
A CSRF token must be:
- Unique for each session
- Hard to guess
We use Node.js's crypto
module:
const crypto = require('crypto');
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex');
}
2. Store the Token in the Session
We will use express-session
to manage the user's session:
const session = require('express-session');
const express = require('express');
const app = express();
app.use(session({
secret: 'a_super_secret_string',
resave: false,
saveUninitialized: true
}));
Every time a user loads a form, we assign a new token if it doesn’t already exist:
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = generateCSRFToken();
}
next();
});
3. Include the Token in the HTML Form
The token is inserted in a hidden field:
<form method="POST" action="/submit">
<input type="hidden" name="csrfToken" value="<%= csrfToken %>">
<input type="text" name="message">
<button type="submit">Send</button>
</form>
In the controller of the route that returns the form, we pass the token:
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.session.csrfToken });
});
4. Validate the Token on the Server
When the form is submitted, the server must compare the received token with the one stored in the session:
app.post('/submit', express.urlencoded({ extended: false }), (req, res) => {
const tokenFromForm = req.body.csrfToken;
if (tokenFromForm !== req.session.csrfToken) {
return res.status(403).send('Invalid CSRF token');
}
// Proceed with the request logic
res.send('Valid request');
});
5. Regenerate the Token After Each Request
For increased security, we can regenerate the CSRF token after each valid request:
req.session.csrfToken = generateCSRFToken();
Conclusion
We have implemented a simple and working CSRF system from scratch in Node.js, without relying on dedicated external libraries. This approach ensures a good level of protection for authenticated forms in environments where full control over the implementation is desired.