CSRF Sync
CSRF Sync is a utility package designed to provide robust stateful Cross-Site Request Forgery (CSRF) protection for Express applications, utilizing the Synchroniser Token Pattern. Developed in response to the deprecation of `csurf` and the perceived complexity or limited scope of alternative solutions, `csrf-sync` (current stable version 4.2.1) aims for a targeted and simplified implementation. It requires a server-side session management middleware like `express-session` to store tokens. The library focuses on providing the essential components for CSRF protection without imposing a full solution, allowing developers to integrate it flexibly. It is actively maintained with regular updates and follows a clear versioning strategy, with significant changes typically highlighted in major version bumps.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'session')
cause The `express-session` middleware (or equivalent) has not been configured or is not placed correctly before the `csrf-sync` middleware.fixEnsure `app.use(session(...))` is called *before* `app.use(csrfSynchronisedProtection)` in your Express application setup. -
Error: Invalid CSRF Token
cause The CSRF token submitted in the request (e.g., via `_csrf` form field or header) does not match the token stored in the user's session, or no token was provided.fixVerify that the client-side code correctly retrieves the token using `generateToken(req)` and includes it in all state-changing requests (e.g., POST, PUT, DELETE). On the server, ensure `csrfSynchronisedProtection` is applied to the routes that require protection. -
TypeError: csrfSync is not a function
cause Incorrect import statement for `csrfSync`. This often happens when treating it as a default export or using an outdated CommonJS pattern after v3.fixFor ESM, use `import { csrfSync } from 'csrf-sync';`. For CommonJS, use `const { csrfSync } = require('csrf-sync');`.
Warnings
- breaking Starting with v4.0.0, `csrf-sync` no longer bundles TypeScript types for `express-session`. Users must explicitly install `express-session` and its types (`@types/express-session`) for TypeScript projects.
- breaking Version 3.0.0 transitioned `csrf-sync` to an ESM-first package. This changed the CommonJS import pattern from attempting a default import to a named import `const { csrfSync } = require('csrf-sync');`.
- gotcha `csrf-sync` fundamentally relies on a stateful session middleware (e.g., `express-session`) that populates `req.session`. Without this middleware correctly configured and placed before `csrfSynchronisedProtection`, the library will fail to store and validate tokens.
- gotcha Improper configuration of the underlying session middleware (e.g., weak session secret, insecure cookie flags) can compromise the effectiveness of CSRF protection provided by `csrf-sync`. The library itself cannot mitigate underlying session vulnerabilities.
- gotcha `csrf-sync` implements the Synchronizer Token Pattern, which requires server-side state. It is not suitable for purely stateless APIs. For stateless scenarios, consider the Double-Submit Cookie Pattern (e.g., using `csrf-csrf`).
Install
-
npm install csrf-sync -
yarn add csrf-sync -
pnpm add csrf-sync
Imports
- csrfSync
const csrfSync = require('csrf-sync');import { csrfSync } from 'csrf-sync'; - csrfSynchronisedProtection
import { csrfSynchronisedProtection } from 'csrf-sync';const { csrfSynchronisedProtection } = csrfSync(); - generateToken
import { generateToken } from 'csrf-sync';const { generateToken } = csrfSync();
Quickstart
import express from 'express';
import session from 'express-session';
import { csrfSync } from 'csrf-sync';
const app = express();
const port = 3000;
// Configure express-session middleware
app.use(session({
secret: process.env.SESSION_SECRET ?? 'super-secret-key-please-change-me',
resave: false,
saveUninitialized: true,
cookie: { httpOnly: true, secure: process.env.NODE_ENV === 'production' }
}));
// Initialize csrf-sync and get the protection middleware and token generator
const { csrfSynchronisedProtection, generateToken } = csrfSync();
// Apply CSRF protection to all routes (or specific ones)
app.use(csrfSynchronisedProtection);
// Middleware to parse URL-encoded bodies for form submissions
app.use(express.urlencoded({ extended: false }));
// Route to display a form with a CSRF token
app.get('/', (req, res) => {
const token = generateToken(req);
res.send(`
<!DOCTYPE html>
<html>
<head><title>CSRF Test</title></head>
<body>
<h1>Submit Form</h1>
<form action="/submit" method="POST">
<input type="hidden" name="_csrf" value="${token}">
<input type="text" name="data" placeholder="Enter data">
<button type="submit">Submit</button>
</form>
</body>
</html>
`);
});
// Protected route to handle form submission
app.post('/submit', (req, res) => {
res.send(`Data received: ${req.body.data} (CSRF protected)`);
});
// Error handling for CSRF issues (optional but recommended)
app.use((err, req, res, next) => {
if (err.code === 'EBADCSRFTOKEN') {
res.status(403).send('Invalid CSRF token - potential attack!');
} else {
next(err);
}
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
if (!process.env.SESSION_SECRET) {
console.warn('WARNING: SESSION_SECRET is not set. Using a default, which is insecure for production.');
}
});