csrf-sync
v4.0.3
Published
A utility package to help implement stateful CSRF protection using the Synchroniser Token Pattern in express.
Downloads
50,979
Readme
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.
getTokenFromRequest, // use this to retrieve the token submitted by a user
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();
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. Note in this case, the route is a get request, and these request types are not protected (ignored request method), as they do not need to be protected so long as the route is not exposing any sensitive actions.
// 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"
app.get("/secret-stuff", csrfSynchronisedProtection, myProtectedRoute);
const myCsrfProtectionMiddleware = (req, res, next) => {
// Some method to determine whether we want CSRF protection to apply
if (isCsrfProtectionNeeded(req)) {
// protect with CSRF
csrfSynchronisedProtection(req, res, next);
} else {
// Don't protect with CSRF
next();
}
};
express.use(myCsrfProtectionMiddleware);
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
.
generateToken(req, true); // This will force a new token to be generated, even if one already exists
req.csrfToken(); // same as generateToken(req) and generateToken(req, false);
req.csrfToken(true); // same as generateToken(req, true);
By default tokens will NOT be revoked, if you want or need to revoke a token you should use this method to do so. Note that if you call generateToken with overwrite set to true, this will revoke the any existing token and only the new one will be valid.
When creating your csrfSync, you have a few options available for configuration, all of them are optional and have sensible defaults (shown below).
const csrfSyncProtection = csrfSync({
ignoredMethods = ["GET", "HEAD", "OPTIONS"],
getTokenFromState = (req) => {
return req.session.csrfToken;
}, // Used to retrieve the token from state.
getTokenFromRequest = (req) => {
return req.headers['x-csrf-token'];
}, // Used to retrieve the token submitted by the request from headers
storeTokenInState = (req, token) => {
req.session.csrfToken = token;
}, // Used to store the token in state.
size = 128, // The size of the generated tokens in bits
});
// NOTE THE VALUES ABOVE ARE THE DEFAULTS.
// THE ABOVE IS THE SAME AS DOING:
const csrfSyncProtection = csrfSync();
If you intend to use this module to protect user submitted forms, then you can use generateToken
to create a token and pass it to your view, likely via template variables. Then using a hidden form input such as the example from the Cheat Sheet.
<form action="/transfer.do" method="post">
<input
type="hidden"
name="CSRFToken"
value="OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjJjZDE1ZDZMGYwMGEwOA=="
/>
[...]
</form>
Upon form submission a csrfSync
configured as follows can be used to protect the form.
const { csrfSynchronisedProtection } = csrfSync({
getTokenFromRequest: (req) => {
return req.body["CSRFToken"];
}, // Used to retrieve the token submitted by the user in a form
});
If using this with something like express
you would need to provide/configure body parsing middleware before the CSRF protection.
If doing this per route, you would for example:
app.post("/route/", csrfSynchronisedProtection, async (req, res) => {
//process the form as we passed CSRF
});
const { csrfSynchronisedProtection } = csrfSync({
getTokenFromRequest: (req) => {
// If the incoming request is a multipart content type
// then get the token from the body.
if (req.is("multipart")) {
return req.body["CSRFToken"];
}
// Otherwise use the header for all other request types
return req.headers["x-csrf-token"];
},
});
(req, res, next) => {
getCsrfTokenAsync(req)
.then((token) => {
req.asyncCsrfToken = token;
next();
})
.catch((error) => next(error));
};
(req) => req.asyncCsrfToken;