6

I'm trying to write a test for my server side form validation but I keep getting a Forbidden error. It seems that this needs to be a 2 step process. Step 1, acquire the CSRF value from the form. Step 2, use the CSRF value to post to the form handler. However no matter how I try to post I get a forbidden error.

--full test: https://github.com/socketwiz/swblog/blob/master/test/contact.js#L57-L100

I've tried changing the following line thusly: https://github.com/socketwiz/swblog/blob/master/test/contact.js#L85

.send({name: 'foo', 'X-CSRF-Token': token})

.set('X-CSRF-Token', token)

.set('Cookie', ['X-CSRF-Token=' + token])

But nothing I try will seem to satisfy the CSRF requirement. The more I try the more complex this gets for what seems like a simple thing. Maybe I'm going about this all wrong. Any ideas?

Ricky Nelson
  • 876
  • 10
  • 24
  • Does this clear anything up? http://jb.demonte.fr/blog/expressjs-angularjs-csrf/ – adrichman Apr 17 '14 at 02:46
  • @adrichman, I guess I should have said that it works just fine from my form, I just can't figure out how to write a test for it. When the test tries to post it gets a Forbidden error no matter what I try :( – Ricky Nelson Apr 17 '14 at 02:52
  • I'm not certain about the syntax you are referencing, but it looks like you may need to refactor so that you are specifically setting an 'x-csrf-token' HEADER equal to the token located on the cookie. – adrichman Apr 17 '14 at 03:00

2 Answers2

17

Thanks @shawnzhu, you comment on cookies helped me figure out what I needed to do. I had an idea that I was over complicating it. Here is what I came up with:

https://github.com/socketwiz/swblog/blob/master/test/contact.js

it('should not post just a name', function(done) {
    request(mock)
      .get('/contact')
      .end(function(err, res){
        var $html = jQuery(res.text);
        var csrf = $html.find('input[name=_csrf]').val();

        request(mock)
          .post('/api/contact.json')
          .set('cookie', res.headers['set-cookie'])
          .send({
            _csrf: csrf,
            name: 'Mary Jane'
          })
          .expect(function(res){
            assert.equal(undefined, res.body.name);
            assert.equal('You must provide a valid email address', res.body.email);
            assert.equal('You must provide a message', res.body.message);
          })
          .expect(500, done);
      });
});
Ricky Nelson
  • 876
  • 10
  • 24
  • This is actually brilliant. I just can't figure out how to get jQuery to run like this. I'm running my tests on node and testing an express app. So I don't really have a full document structure. I added: var jQuery = require('jQuery'); Error is: Uncaught Error: jQuery requires a window with a document – user2562923 May 14 '15 at 17:07
  • I think you need to require something like jsdom, check the answers to this question: http://stackoverflow.com/questions/21358015/error-jquery-requires-a-window-with-a-document – Ricky Nelson May 14 '15 at 20:53
  • Or check this out on how to get the csrf token out of the cookie better: http://stackoverflow.com/questions/18773846/how-to-test-endpoints-protected-by-csrf-in-node-js-express – Wtower Aug 15 '16 at 09:54
1

The express csrf middleware saves a secret in session to validate csrf token, while I guess you use cookieSession middleware as session store. So you need to resend the session cookies when POST the data with csrf token, the express can use the secret in session to validate your csrf token.

shawnzhu
  • 7,233
  • 4
  • 35
  • 51