📅 You're Invited: Meet the Socket team at RSAC (April 28 – May 1).RSVP
Socket
Sign inDemoInstall
Socket

csrf-csrf

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

csrf-csrf

A utility package to help implement stateless CSRF protection using the Double Submit Cookie Pattern in express.

4.0.0
latest
Source
npm
Version published
Weekly downloads
34K
-16.35%
Maintainers
1
Weekly downloads
 
Created
Source

Double CSRF

Coverage Status

Getting StartedConfigurationUtilitiesFAQSupport

Background

This module provides the necessary pieces required to implement CSRF protection using the Double Submit Cookie Pattern. This is a stateless CSRF protection pattern, if you are using sessions it is highly recommended that you use csrf-sync for the Synchroniser Token Pattern instead.

Since csurf has been deprecated I struggled to find alternative solutions that were accurately implemented and configurable, so I decided to write my own! Thanks to NextAuth as I referenced their implementation. From experience CSRF protection libraries tend to complicate their configuration, and if misconfigured, can render the protection completely useless.

This is why csrf-csrf aims to provide a simple and targeted implementation to simplify its use.

Getting Started

Version 4 is now live! If you are upgrading from version 3 check the changelog, the upgrade guide, and the updated configuration documentation below.

Before getting started with csrf-csrf you should consult the FAQ and determine whether you need CSRF protection and whether csrf-csrf is the right choice.

This section will guide you through using the default setup, which sufficiently implements the Double Submit Cookie Pattern. If you would like to customise the configuration, see the configuration section.

You will need to have the cookie-parser middleware registered before the doubleCsrfProtection middleware. If you are using express-session then it is also best to register the cookie-parser middleware after that.

This utility will set a cookie containing a hmac based CSRF token, the frontend should include this CSRF token in an appropriate request header or in the body. The getTokenFromRequest option should strictly return the CSRF token from either a request header, or the request body. If you have cases where you need to consider both make these as explicit as possible to avoid the same vulnerability as csurf, do not just use fallthroughs (||, ??).

If you are using TypeScript, requires TypeScript >= 3.8

npm install cookie-parser csrf-csrf
// ESM
import { doubleCsrf } from "csrf-csrf";

// CommonJS
const { doubleCsrf } = require("csrf-csrf");
const {
  invalidCsrfTokenError, // This is just for convenience if you plan on making your own middleware.
  generateCsrfToken, // Use this in your routes to provide a CSRF token.
  validateRequest, // Also a convenience if you plan on making your own middleware.
  doubleCsrfProtection, // This is the default CSRF protection middleware.
} = doubleCsrf({
  getSecret = (req) => 'return some cryptographically pseudorandom secret here',
  getSessionIdentifier = (req) => req.session.id // return the requests unique identifier
});

This will extract the default utilities, you can configure these and re-export them from your own module. Primarily csrf-csrf was built to support Single Page Applications (SPAs), or frontends hosted cross-site to their backend, the default configuration is optimised for this usecase. If you are changing any of the configuration you should ensure you understand the impact of the change. Consult the documentation for the respective configuration option and also consider reading the FAQ.

To create a route which generates a CSRF token and a cookie containing the CSRF token:

const myRoute = (req, res) => {
  const csrfToken = generateCsrfToken(req, res);
  // You could also pass the token into the context of a HTML response.
  res.json({ csrfToken });
};
const myProtectedRoute = (req, res) =>
  res.json({ unpopularOpinion: "Game of Thrones was amazing" });

Instead of importing and using generateCsrfToken, you can also use req.csrfToken any time after the doubleCsrfProtection middleware has executed on a request.

request.csrfToken(); // same as generateCsrfToken(req, res);

You can also put the token into the context of a templated HTML response.

// Make sure your session middleware is registered before these
express.use(session);
express.use(cookieParser);
express.get("/csrf-token", myRoute);
express.use(doubleCsrfProtection);
// Any non GET routes registered after this will be considered "protected"

By default, any requests that are not GET, HEAD, or OPTIONS methods will be protected. You can configure this with the ignoredMethods option. Keep in mind that only requests with side effects need to be protected, it is generally bad practice to have side effects from GET requests.

You can also protect routes on a case-to-case basis:

app.get("/secret-stuff", doubleCsrfProtection, myProtectedRoute);

Once a route is protected, you will need to ensure the CSRF token cookie is sent along with the request and by default you will need to include the CSRF token in the x-csrf-token header, otherwise you will receive a `403 - ForbiddenError: invalid csrf token`. If your cookie is not being included in your requests be sure to check your withCredentials, CORs configuration, and ensure appropriate sameSite configuration for your use case. For additional information on figuring out the error see the "Dealing with 'ForbiddenError: invalid csrf token'" section of the FAQ.

Sessions

Once again, if you are using sessions, you should be using csrf-sync instead. Sessions have server side state by default.

If you do plan on using express-session with csrf-csrf then ensure your cookie-parser middleware is registered after express-session, as express-session parses its own cookies and may conflict.

Using asynchronously

csrf-csrf itself will not support promises or async, however there is a way around this. If your CSRF token is stored externally and needs to be retrieved asynchronously, you can register an asynchronous middleware first, which exposes the token.

(req, res, next) => {
  getCsrfTokenAsync(req)
    .then((token) => {
      req.asyncCsrfToken = token;
      next();
    })
    .catch((error) => next(error));
};

And in this example, your getCsrfTokenFromRequest would look like this:

(req) => req.asyncCsrfToken;

Configuration

When initialising doubleCsrf, you have a lot of options available for configuration, the only required options are getSecret and getSessionIdentifier, the rest have sensible defaults (shown below).

const doubleCsrfUtilities = doubleCsrf({
  getSecret: () => "Secret", // A function that optionally takes the request and returns a secret
  getSessionIdentifier: (req) => req.session.id, // A function that returns the session identifier for the request
  cookieName: "__Host-psifi.x-csrf-token", // The name of the cookie to be used, recommend using Host prefix.
  cookieOptions: {
    sameSite = "strict",
    path = "/",
    secure = true,
    httpOnly = true,
    ...remainingCookieOptions // See cookieOptions below
  },
  size: 32, // The size of the random value used to construct the message used for hmac generation
  ignoredMethods: ["GET", "HEAD", "OPTIONS"], // A list of request methods that will not be protected.
  getCsrfTokenFromRequest: (req) => req.headers["x-csrf-token"], // A function that returns the token from the request
  skipCsrfProtection: undefined
});

getSecret

(request?: Request) => string | string[]

Required

This should return a secret key or an array of secret keys to be used for hmac generation. Secret keys should be cryptographically pseudorandomly generated. You should make sure you use a strong and secure secret key. See the "How to secret?" section of the FAQ.

In case multiple are provided, the first one will be used for hashing. For validation, all secrets will be tried, preferring the first one in the array. Having multiple valid secrets can be useful when you need to rotate secrets, but you do not want to invalidate the previous secret (which might still be used by some users) right away.

getSessionIdentifier

(request: Request) => string;

Required

This function should return the session identifier for the incoming request. This is used as part of the message used to generate the hmac, it ensures that generated CSRF tokens can only be used by the sessions that originally requested them.

If you are rotating your sessions (which you should be), you will need to ensure a new CSRF token is generated at the same time. This should typically be done when a session has some sort of authorisation elevation (e.g. signed in, signed out, sudo).

cookieName

string;

Optional
Default: "__Host-psifi.x-csrf-token"

The name of the cookie that will be used to track the CSRF token. If you change this it is recommend that you continue to use the __Host- or __Secure- security prefix for production.

Change for development

The security prefix requires the secure flag to be true and requires requests to be received via HTTPS, unless you have your local instance running via HTTPS, you will need to change this value in your development environment.

cookieOptions

{
  sameSite?: string;
  path?: string;
  secure?: boolean
  ...remainingCookieOptions // See below.
}

Optional
Default:

{
  sameSite: "strict",
  path: "/",
  secure: true,
  httpOnly: true
}

The options provided to the cookie, see cookie attributes. If you plan on changing the httpOnly to false, see the "Does httpOnly have to be true" section of the FAQ. The remaining options are all undefined by default and consist of:

  maxAge?: number | undefined;
  expires?: Date | undefined;
  domain?: string | undefined;
  encode?: (val: string) => string

Note that the signed cookie option is not available, it is redundant here. The CSRF tokens generated by csrf-csrf are already signed

messageDelimiter

string;

Optional
Default: "!"

The messageDelimiter is used when concatenating the message that will be used for hmac generation. This should be different to the csrfTokenDelimiter

csrfTokenDelimiter

string;

Optional
Default: "."

The csrfTokenDelimiter is used to concatenate the hmac and the random value to construct the CSRF token. The random value portion is used to reconstruct the hmac during validation and helps prevent collisions.

getCsrfTokenFromRequest

(req: Request) => string | null | undefined;

Optional
Default:

(req: Request) => req.headers["x-csrf-token"];

This function should return the token sent by the frontend, either in the request body/payload, or from the x-csrf-token header. Do NOT return the value from the cookie in this function, this would be the same as having no csrf protection at all, see the "Why is using the cookie in getTokenFromRequest a bad idea?" section of the FAQ.

hmacAlgorithm

string;

Optional
Default: "sha256"

The algorithm passed to the createHmac call when generating a token.

ignoredMethods

Array<CsrfRequestMethod>;

Optional
Default:
["GET", "HEAD", "OPTIONS"]

An array of request methods that the doubleCsrfProtection middleware will ignore, requests made matching these request methods will not be protected. It is recommended you leave this as the default. If you have GET requests with side effects and need those protected, consider route based protection and/or making use of skipCsrfProtection by skipping for all GET requests except for those that need it.

size

number;

Optional
Default:
32

The size in bytes of the random value that will be generated and used for hmac generation.

errorConfig

statusCode?: number;
message?: string;
code?: string | undefined;

Optional
Default:

{
  statusCode: 403,
  message: "invalid csrf token",
  code: "EBADCSRFTOKEN"
}

Used to customise the error response statusCode, the contained error message, and its code, the error is constructed via createHttpError. The default values match that of csurf for convenience.

skipCsrfProtection

(req: Request) => boolean;

Optional - Use this option with extreme caution*

Used to determine whether CSRF protection should be skipped for the given request. If this callback is provided and the request is not in the ignoredMethods, then the callback will be called to determine whether or not CSRF token validation should be checked. If it returns true the CSRF protection will be skipped, if it returns false then CSRF protection will be checked.

* It is primarily provided to avoid the need of wrapping the doubleCsrfProtection middleware in your own middleware, allowing you to apply a global logic as to whether or not CSRF protection should be executed based on the incoming request. You should only skip CSRF protection for cases you are 100% certain it is safe to do so, for example, requests you have identified as coming from a native app. You should ensure you are not introducing any vulnerabilities that would allow your web based app to circumvent the protection via CSRF attacks. This option is NOT a solution for CSRF errors.

Utilities

Below is the documentation for what doubleCsrf returns.

doubleCsrfProtection

(request: Request, response: Response, next: NextFunction) => void

The middleware used to actually protect your routes, see the getting started examples above, or the examples included in the repository.

generateCsrfToken

(
  request: Request,
  response: Response,
  {
    cookieOptions?: CookieOptions, // allows overriding of cookieOptions
    overwrite?: boolean, // Set to true to force a new token to be generated
    validateOnReuse?: boolean, // Set to true to throw an error when overwrite is false and the current CSRF token is invalid
  } // optional
) => string;

By default if a csrf-csrf cookie already exists on an incoming request, generateCsrfToken will not overwrite it, it will return the existing token so long as the token is valid. If you wish to force a token generation, you can use the overwrite option of the third parameter:

generateCsrfToken(req, res, { overwrite: true }); // This will force a new token to be generated, and a new cookie to be set, even if one already exists

If the overwrite parameter is set to false (default), the existing token will be re-used and returned. If the current / existing CSRF token is not valid, then a new token will be generated without any error being thrown. If you want the generateCsrfToken to throw an error instead, provide the validateOnReuse: true option.

If overwrite is true a new token will always be generated, even if the current one is invalid.

generateCsrfToken(req, res, { overwrite: true }); // As overwrite is true, an error will never be thrown.
generateCsrfToken(req, res, { overwrite: false }); // As validateOnReuse is false (default), if the current CSRF token from the cookie is invalid, a new token will be generated without any error being thrown.
generateCsrfToken(req, res); // same as previous
generateCsrfToken(req, res, { overwrite: false, validateOnReuse: true }); // As validateOnReuse is true, if the CSRF token from the cookie is invalid, a new token will be generated without an error being thrown.

Instead of importing and using generateCsrfToken, you can also use req.csrfToken any time after the doubleCsrfProtection middleware has executed on your incoming request.

req.csrfToken(); // same as generateCsrfToken(req, res);
req.csrfToken({ overwrite: true }); // same as generateCsrfToken(req, res, { overwrite: true, validateOnReuse });
req.csrfToken({ overwrite: false, validateOnReuse: false }); // same as generateCsrfToken(req, res, { overwrite: false, validateOnReuse: false });
req.csrfToken(req, res, { overwrite: false });
req.csrfToken(req, res, { overwrite: false, validateOnReuse: false });

The generateCsrfToken function serves the purpose of establishing a CSRF protection mechanism by generating a token and an associated cookie. This function also provides the option to utilise a third parameter called overwrite, and a fourth parameter called validateOnReuse. By default, overwrite and validateOnReuse are both set to false.

It returns a CSRF token and attaches a cookie to the response object. The cookie content is `${hmac}${csrfTokenDelimiter}${randomValue}`.

In some cases you should only transmit your token to the frontend as part of a response payload. Consult the "Do I need csrf-csrf?" and "Does httpOnly have to be true?" sections of the FAQ.

When overwrite is set to false, the function behaves in a way that preserves the existing CSRF token. In other words, if a valid CSRF token is already present in the incoming request cookie, the function will reuse the existing CSRF token.

If overwrite is set to true, the function will generate a new token and cookie each time it is invoked. This behavior can potentially lead to certain complications, particularly when multiple tabs are being used to interact with your web application. In such scenarios, the creation of new cookies with every call to the function can disrupt the proper functioning of your web app across different tabs, as the changes might not be synchronised effectively (you would need to write your own synchronisation logic).

If overwrite is set to false, the function will also validate the existing cookie information. If the information is found to be invalid, a new token will be generated and returned. If you want an error to be thrown when validation fails during generation you can set the validateOnReuse (by default, false) to true. If it is true then an error will be thrown instead of a new token being generated.

invalidCsrfTokenError

This is the error instance that gets passed as an error to the next call, by default this will be handled by the default error handler. This error is customisable via the errorConfig.

validateRequest

(req: Request) => boolean;

This function is used by the doubleCsrfProtection middleware to determine whether an incoming request has a valid CSRF token. You can use this to make your own custom middleware (not recommended).

Support

  • Join the Discord and ask for help in the psifi-support channel.
  • Pledge your support through the Patreon

Buy Me a Coffee at ko-fi.com

Keywords

csrf

FAQs

Package last updated on 27 Apr 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts