A utility package to help implement stateful CSRF protection using the Synchroniser Token Pattern in express.
Dos and Don'ts • Getting Started • Configuration • Support
This module intends to provide the necessary pieces required to implement CSRF protection using the Synchroniser Token Pattern. This means you will require server side state, if you require stateless CSRF protection, please see csrf-csrf for the Double-Submit Cookie Pattern.
Since csurf has been deprecated I struggled to find alternative solutions that were accurately implemented and configurable, so I decided to write my own! A lot of CSRF protection based packages often try to provide a full solution, or they only provide the Double-Submit Cookie method, and in doing so, they become rather complicated to configure. So much so, that your configuration alone could render the protection completely useless.
This is why csrf-sync aims to provide a single and targeted implementation to simplify it's use.
- Do read the OWASP - Cross-Site Request Forgery Prevention Cheat Sheet
- Do read the OWASP - Session Management Cheat Sheet
-
Do join the Discord server and ask questions in the
psifi-support
channel if you need help. - Do make sure you do not compromise your security by not following OWASP practices.
- Do not transmit your CSRF token by cookies.
- Do not include your CSRF tokens in any log output.
- Do not use the same unauthenticated session for a user after they have authenticated. Make sure you destroy the session and create a new one. If they logout, destroy the session and create a new one. Keep in mind any generated token will be lost once a session is destroyed.
This section will guide you through using the default setup, which does sufficiently implement the Synchronised Token Pattern. If you'd like to customise the configuration, see the configuration section.
You will need to be using express-session (or a session middleware which provides a request.session
property). this utility will add a csrfToken
property to request.session
.
npm install express express-session csrf-sync
// ESM
import { csrfSync } from "csrf-sync";
// CommonJS
const { csrfSYnc } = require("csrf-sync");
const {
invalidCsrfTokenError, // This is just for convenience if you plan on making your own middleware.
generateToken, // Use this in your routes to generate, store, and get a CSRF token.
getTokenFromState, // The default method for retrieving a token from state.
storeTokenInState, // The default method for storing a token in state.
revokeToken, // Revokes/deletes a token by calling storeTokenInState(undefined)
csrfSynchronisedProtection, // This is the default CSRF protection middleware.
} = csrfSync();
This will extract the default utilities, you can configure these and re-export them from your own module. You should only transmit your token to the frontend as part of a response payload, do not include the token in response headers or in a cookie.
This means you will need to create your own route(s) for generating and retrieving a token. For example, a JSON endpoint which you can call before making form submissions:
const myRoute = (req, res) => res.json({ token: generateToken(req) });
const myProtectedRoute = (req, res) =>
res.json({ unpopularOpinion: "Game of Thrones was amazing" });
You can also put the token into the context of a templated HTML response. Just make sure you register this route before registering the middleware so you don't block yourself from getting a token.
// Make sure your session middleware is registered before these
express.use(session);
express.get("/csrf-token", myRoute);
express.use(csrfSynchronisedProtection);
// Anything registered after this will be considered "protected"
In most cases, it's unlikely you want to protect everything, so you can protect your routes on a case-to-case basis:
app.get("/secret-stuff", csrfSynchronisedProtection, myProtectedRoute);
Or you can conditionally wrap the middleware yourself, like so (basic example):
const myCsrfProtectionMiddleware = () => {
const ignoremethods = new Set(["GET", "HEAD", "OPTIONS"]);
return (req, res, next) => {
if (ignoreMethods.has(req.method)) {
next();
} else {
csrfSynchronisedProtection(req, res, next);
}
};
};
express.use(myCsrfProtectionMiddleware());
And now this will only require a CSRF token to be present for requests that aren't GET
, HEAD
, or OPTIONS
.
Once a route is protected, you will need to include the most recently generated token in the x-csrf-token
request header, otherwise you'll receive a 403 - ForbiddenError: invalid csrf token
.
When creating your csrfSync, you have a few options available for configuration, all of them are optional and have sensible defaults (shown below).
const csrfSync = csrfSync({
getTokenFromState = (req) => {
return req.session.csrfToken;
}, // Used to retrieve the token from state.
storeTokenInState = (req, token) => {
req.session.csrfToken = token;
}, // Used to store the token in state.
header = "x-csrf-token", // The header name where the token is on incoming requests.
size = 128, // The size of the generated tokens in bits
}):