expressjs / csurf

CSRF token middleware

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Update docs to address situations with mixed protection approaches

bradtaniguchi opened this issue · comments

commented

Hello, I've spent a significant portion of my life dealing with the following error:

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

After smashing my head against this error debugging I've found that I was making a mistake between the different approaches to protecting against csrf attacks. Namely mixing this librarie's double submit cookie pattern and my client framework (Angular) default's approach cookie-to-header token pattern, which results in the above error.

There can be multitude of reasons the above error appears, but the nature to which it occurs seems like it should work at a glance, as technically each side is doing things correctly.

I might not be the best networking or security guy, but I was able to figure this out and feel like it was much more of a pain simply due to me not paying attention to what I think are a few glossed over but critical details.


The Wrong configuration

My server-side was configured with the following:

import csrf from 'csurf';
//.. where middlewares are defined on my server-side
csrf({
  cookie: {
    // according to the docs, this implements the double cookie pattern
    // so this will be the name of the cookie to store the token secret
    key: 'XSRF-TOKEN' 
  }
})(req, res, (err) => {
  if (err) {
    logger.debug(err); // above error appears here
    // re-throw with my own error to handle it elsewhere
    throw new CsrfError();
  }
  next();
})

//.. elsewhere in a GET controller/route I send the XSRF-TOKEN csrf "expects":
return res.cookie('XSRF-TOKEN', req.csrfToken()).send(/*stuff*/);

I was using a nestjs setup, so my middlewares where defined slightly different than a stock express app, but I digress. I just want to provide as much context to this as possible. For an express app you would probably have something like:

const csrf = require('csurf');
//...
app.use(cookieParser()); // requires cookie parser due to this needing a cookie
app.use(csrf({
  key: 'XSRF-TOKEN'
}));

From here on out I'll stick to express examples, as such a setup would apply to more people. There might be slight syntax issues though.


On the client-side I was using Angular, which as the docs state:

When performing HTTP requests, an interceptor reads a token from a cookie, by default XSRF-TOKEN...

Without reading any further, one might assume this means Angular will automatically grab the XSRF-TOKEN and magic security stuff will automatically happen, except that isn't what happens. Once you make POST requests, csurf gives the above error, even though it would appear at a glance everything is configured correctly.
Both sides are configured correctly, except they are configured differently at this point, which results in the above error.

If one were to continue reading the Angular docs mentioned above, they would continue:

...and sets it as an HTTP header, X-XSRF-TOKEN.

Which will be set and being sent to the server-side where it promptly explodes with the above error. Even though at a glance, the header: X-XSRF-TOKEN and the cookie: XSRF-TOKEN will be the same. Which is actually wrong, as csurf doesn't expect it due to the way its configured it expects double cookies.


The correct configuration:

The correct configuration for this situation is actually set csrf to be the following:

const csrf = require('csurf');
app.use(csrf()); // without the cookie property, csrf will follow the cookie-to-header pattern by default

which by default will follow the cookie-to-header token pattern, which is what Angular does.
Later in a route you still set the cookie as already mentioned:

// in a GET
return res.cookie('XSRF-TOKEN', req.csrfToken()).send(/*stuff*/);

And bam POST requests now go thru as expected.


Soooo whats the point of all that?

Can we update the docs to point out/warn against using the double submit cookie pattern for SPA applications like Angular? Or mixing the two approaches?

If anything I hope this issue helps document such a case/setup and the potential pitfalls of setting stuff up as such.

ref:

There is no issue using the double submit pattern with angular. The issue in your code above is that you are trying to set two different values to the same cookie name: the cookie.key configuration is (in the words of the docs here) "the name of the cookie to use to store the token secret". You are using the name XSRF-TOKEN for this cookie, which is probably confusing, as this specifies the token secret, not the token itself. Then later in your code, you use res.cookie to set a different value to that same cookie name, namely you are saving the csrf token to that value.

The solution is to not use the same cookie name, as outlined in our docs. I understand that if you are following tutorials from other tutorials and blogs they create a lot of confusion, but it's unfortunate we cannot control those.

But there is no issue using double-submit cookie with Angular if you want to use that pattern -- but you can only store a single value in a given cookie per web browser restrictions. Don't choose to store two different values in the same cookie name :)

Here would be a "fixed" version of your config:

import csrf from 'csurf';
//.. where middlewares are defined on my server-side
csrf({
  cookie: {
    // according to the docs, this implements the double cookie pattern
    // so this will be the name of the cookie to store the token secret
    key: 'XSRF-SECRET' 
  }
})(req, res, (err) => {
  if (err) {
    logger.debug(err); // above error appears here
    // re-throw with my own error to handle it elsewhere
    throw new CsrfError();
  }
  next();
})

//.. elsewhere in a GET controller/route I send the XSRF-TOKEN csrf "expects":
return res.cookie('XSRF-TOKEN', req.csrfToken()).send(/*stuff*/);