expressjs / csurf

CSRF token middleware

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Documentation on using with Ajax

george-norris-salesforce opened this issue · comments

Trying to use this with Ajax and it is not working.
Many others are having same problem.
This is a common use case and would be worthy of putting in the documentation..

Hi @george-norris-salesforce I think that's a good suggestion :) I haven't personally used it very much with Ajax, so anyone who wants to provide a nice example / docs would be awesome 👍

Ok maybe we can start fleshing out what to do here..

I'm doing this, and it is not working.. ↓↓↓↓

Sending cookie on index route..

//server

app.use(cookieParser());
const csrfProtection = csrf({ cookie: true });
app.get('*', csrfProtection, (req, res) => {
  res.sendFile(
    resolve(__dirname, '../build/index.html'),
    { csrfToken: req.csrfToken() }
  );
});

Cookie is stored correctly on the client..
image

sending cookie ok via Ajax.. it does get sent to the server in the headers.
image

The route that recieves request..
app.post(`upload_photo`,
  uploadHandler.single('file'),
  csrfProtection,
  (req, res) => {
    .....

Results in 403

Tried changing headers to ..

req.headers['csrf-token']
req.headers['XSRF-Token']
req.headers['x-csrf-token]

Still results in 403

The cookie in your example only is storing the token secret, not the token itself. You get the token from req.csrfToken() and that is the value you would need to send back to the server.

Unless I'm not understanding this example, the req.csrfToken() is used to initially set the cookie and the protected routes use the csrfProtection middleware to look for a header with one of these names. The example appears to do be doing this too..

Here is what I'm doing step by step..

1). The index get route (which is always first route in single page app) is hit. req.csrfToken() is called and sends back token. Similar to /form get route in this example.

2). Instead of using a hidden field in a standard form submit, we read cookie from browser and add it to headers and make ajax request.. the header name keeps with these conventions.

3). csrfProtection is inserted as middleware in the route, similar to the /process route in this example.

The example is using the value from the req.csrfToken() call via the form element, which is the token. You had no code doing that. The readme explains that the _csrf cookie is the token secret, not the token.

Basically the token is not the same as the token secret, they are different value. The value you need to put in the header back had to be the value you got from the req.csrfToken() call, not from the _csrf cookie, which is not the same value and will be rejected.

@dougwilson I'm looking to implement something similar too and was confused when reading through this issue.

In the example from the README.md, you're saying the req.csrfToken() is generating the actual token.

But in the code snippet posted by @george-norris-salesforce, the req.csrfToken() call he is using is only creating a token secret?

If so, why the difference? And how would I generate the actual token if I only have the token secret?

Hi @newyork-anthonyng the token is generated from req.csrfToken() call, which is the value you need to submit back to the server. The code snippet from @george-norris-salesforce is not correct.

@dougwilson

I see in his code snippet that he's using:

app.get('*', csrfProtection, (req, res) => {
  res.sendFile(
    resolve(__dirname, '../build/index.html'),
    { csrfToken: req.csrfToken() }
  );
});

The req.csrfToken() is there. I might be missing something obvious here.

Hi @newyork-anthonyng the res.sendFile method as defined in the Express.js API does not take a csrfToken option, so that option is doing nothing. You may as well be doing res.sendFile(file, { fooBarBaz: req.csrfToken() }).

You can see the list of options the res.sendFile method takes here: http://expressjs.com/en/4x/api.html#res.sendFile

@dougwilson Thanks! I totally missed the res.sendFile part.

@george-norris-salesforce Did you start on the PR for updating the docs? If not, I'd like to help.

Does anyone have this working that can share and example?

I'm having the same problem; Making a react SPA with webpack and hosting on node/express, need a way to let the view get req.csrfToken()

One thing that seems to be left out is that you need to pass both the csrfToken AND the _csrf cookie.

The cookie should be found in the response.headers['set-cookie'].

@phun-ky Angular automatically looks for a cookie with the name XSRF-TOKEN and then translates it to an X-XSRF-token header. I don't know if there's a React library that does this. The following may help you out a bit.

app.use(cookieParser())
app.use(csurf({ cookie: true }))
app.use(function (req, res, next) {
  if (!req.cookies['_csrf']) return next();

  res.setHeader('Set-Cookie', [
    'XSRF-TOKEN='+ req.csrfToken() + ';' +
    'path=/'
    ]);

  next();

})
app.use(function (err, req, res, next) {

  if (err.code !== 'EBADCSRFTOKEN') {
    return next(err)
  } else {
    res.writeHeader(403);
    res.write(err.toString());
    res.end();
  }

})

@phun-ky Angular automatically looks for a cookie with the name XSRF-TOKEN and then translates it to an X-XSRF-token header. I don't know if there's a React library that does this. The following may help you out a bit.

React doesn't do HTTP requests. You use whatever XHR/fetch api you want.

axios does something similar with a cookie and header.

I was having this issue too but realised I wasn't sending the token secret with my ajax call.

Sending the token through a header wasn't enough, you also need to send the token secret which is stored in the _csrf cookie or in req.session (depending on whether you set 'cookie' to true or false in the middleware options)

The fix for me was to include the credentials (which sends the cookie / session data) with the ajax request. I'm using the fetch api but there are ways to do this with jQuery, axios etc. If this is the fix you're looking for I'm happy to do a PR updating the docs.

Example:

/* Set a header including the token */
const headers = new Headers();
headers.append('csrf-token', _token_);   // _token_ represents the token returned from req.csrfToken()

/* Make the request */
fetch('/protectedAPI', {
  credentials: 'same-origin',   // Sends the cookie which includes the token secret
  headers: headers,
  method: 'POST'
});
commented

@AndrewHaine,

It seems like you are saying that the solution is to store both the csrf token AND secret as cookies in the client. But doesn't this defeat the purpose of the CSRF protection?

The idea is you shouldn't be able to make a request simply by using the cookies stored in a client. The idea is supposed to be that a request requires a cookie in the client AND a value from the submitted request to succeed.