JavaScript > Security > Common Vulnerabilities > Cross-Site Request Forgery (CSRF)
CSRF Protection with Synchronizer Token Pattern
This code demonstrates a basic implementation of CSRF protection using the Synchronizer Token Pattern in JavaScript. This pattern involves generating a unique token on the server, embedding it in a form or request, and then verifying it on the server side to ensure the request originated from the actual user.
Concepts Behind CSRF Protection
Cross-Site Request Forgery (CSRF) is a type of malicious exploit where unauthorized commands are transmitted from a user that the website trusts. CSRF attacks work by tricking a browser into sending a forged request to a server. The Synchronizer Token Pattern (STP), also known as CSRF token, is a common and effective defense against CSRF attacks. The server generates a unique, unpredictable token and associates it with the user's session. This token is included as a hidden field in forms or as a custom header in AJAX requests. When the server receives a request, it verifies that the submitted token matches the token stored in the user's session. If they don't match, the request is rejected.
Generating and Embedding the CSRF Token (Server-Side - Example in Node.js with Express)
This Node.js example using Express demonstrates how to generate and embed a CSRF token. First, we use `express-session` to manage user sessions. A middleware function generates a unique CSRF token and stores it in the user's session if it doesn't already exist. This token is then made available to the template engine (here, simply embedded in the HTML). The `/form` route sends an HTML form that includes the CSRF token as a hidden input field. The `/submit` route receives the form submission and verifies the submitted CSRF token against the token stored in the session. If the tokens match, the request is processed; otherwise, it's rejected with a 403 Forbidden error. It's important to regenerate the CSRF token after a successful submission to prevent replay attacks.
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');
const app = express();
// Configure session middleware
app.use(session({
secret: 'your-secret-key', // Replace with a strong, random secret
resave: false,
saveUninitialized: true,
cookie: { secure: false } // Set to true in production if using HTTPS
}));
// Middleware to generate and set CSRF token
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
}
res.locals.csrfToken = req.session.csrfToken;
next();
});
// Route to display a form with the CSRF token
app.get('/form', (req, res) => {
res.send(`
<form action="/submit" method="POST">
<input type="hidden" name="csrfToken" value="${res.locals.csrfToken}">
<label for="data">Enter Data:</label>
<input type="text" id="data" name="data">
<button type="submit">Submit</button>
</form>
`);
});
// Route to handle form submission and verify the CSRF token
app.post('/submit', (req, res) => {
//This part required body-parser or express.urlencoded({ extended: true })
//For simplicity we skip
const submittedToken = req.body.csrfToken;
if (submittedToken && submittedToken === req.session.csrfToken) {
// CSRF token is valid, process the request
res.send('Request processed successfully!');
// Regenerate CSRF token after successful submission
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
res.locals.csrfToken = req.session.csrfToken; //Update token in case it is used on the same page
} else {
// CSRF token is invalid, reject the request
res.status(403).send('CSRF token validation failed.');
}
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
Verifying the CSRF Token (Server-Side - Example in Node.js with Express)
The server-side code (as shown above in the combined snippet) is responsible for verifying the CSRF token. The server compares the token received in the request (e.g., from a hidden form field or request header) with the token stored in the user's session. If the tokens match, the request is considered legitimate. If they do not match, the request is rejected, preventing the CSRF attack.
// Already showed in the previous snippet
Client-Side (JavaScript - Example with Fetch API)
This JavaScript snippet demonstrates how to include the CSRF token in an AJAX request using the Fetch API. The token is retrieved from the HTML (e.g., from a hidden input field). It's included in the request headers (here, using the `X-CSRF-Token` custom header). The server needs to be configured to expect and validate this header. Using custom headers for CSRF tokens is generally preferred over including them in the request body to avoid accidentally leaking the token through URL parameters or server logs.
async function submitForm(data) {
const csrfToken = document.querySelector('input[name="csrfToken"]').value;
try {
const response = await fetch('/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken // Using a custom header
},
body: JSON.stringify(data)
});
if (response.ok) {
console.log('Form submitted successfully!');
} else {
console.error('Form submission failed:', response.status);
}
} catch (error) {
console.error('Error submitting form:', error);
}
}
Real-Life Use Case
Imagine a banking website where users can transfer funds. Without CSRF protection, an attacker could trick a user into unknowingly submitting a transfer request. By embedding a malicious link or script on a different website, the attacker could construct a request that, when clicked by the authenticated user, transfers funds to the attacker's account. CSRF protection, using the Synchronizer Token Pattern, would prevent this by ensuring that only requests originating from the legitimate banking website, with the correct CSRF token, are processed.
Best Practices
Interview Tip
When discussing CSRF during an interview, be prepared to explain: What CSRF is, How it works, The Synchronizer Token Pattern (or other mitigation techniques), The importance of server-side validation, and The limitations of relying solely on client-side JavaScript for security.
When to Use
Use CSRF protection on any application that performs state-changing operations (e.g., updating profiles, transferring funds, posting comments). If an attacker can trick a user into performing an action, CSRF protection is essential. It is especially critical for applications dealing with sensitive data or financial transactions.
Memory Footprint
The Synchronizer Token Pattern has a relatively small memory footprint. The CSRF token itself is typically a short string (e.g., 32-64 characters). The primary memory overhead comes from storing the token in the user's session on the server. Session management libraries are generally optimized for this purpose. The additional memory usage is usually negligible compared to the overall application requirements.
Alternatives
Pros
Cons
FAQ
-
What happens if the CSRF token is not unique per session?
If the CSRF token is not unique per session, it becomes predictable and an attacker can easily forge requests, defeating the purpose of CSRF protection. -
Is it safe to store the CSRF token in a JavaScript variable?
Storing the CSRF token in a JavaScript variable exposes it to potential XSS attacks. It's generally safer to retrieve the token directly from the DOM when needed, or to use HTTP-only cookies (with the Double Submit Cookie pattern) to prevent JavaScript access altogether.