如何在node.js / express中测试受csrf保护的端点

时间:2013-09-12 20:33:17

标签: node.js angularjs express request csrf

我已经在快递中实施了csrf(跨站点请求伪造)保护:

...
app.use(express.csrf());
app.use(function (req, res, next) {
  res.cookie('XSRF-TOKEN', req.csrfToken());
  next();
});
...

这很有效。 Angularjs在通过$ http服务发出的所有请求中使用了csrf令牌。我通过我的角度应用程序发出的请求很有效。

我的问题是测试这些api端点。我正在使用mocha运行我的自动化测试和请求模块来测试我的api端点。当我使用请求模块向使用csrf(POST,PUT,DELETE等)的端点发出请求时,即使它正确地使用了cookie等,它也会失败。

还有其他人想出解决方案吗?有人需要更多信息吗?

测试示例:

function testLogin(done) {
  request({
    method: 'POST',
    url: baseUrl + '/api/login',
    json: {
      email: 'myemail@email.com',
      password: 'mypassword'
    } 
  }, function (err, res, body) {
    // do stuff to validate returned data
    // the server spits back a 'FORBIDDEN' string,
    // which obviously will not pass my validation
    // criteria
    done();
  });
}

3 个答案:

答案 0 :(得分:10)

诀窍是你需要将你的POST测试包装在GET中并从cookie中解析必要的CSRF令牌。首先,假设您创建一个与Angular兼容的CSRF cookie,如下所示:

.use(express.csrf())
.use(function (req, res, next) {
  res.cookie('XSRF-TOKEN', req.session._csrf);
  res.locals.csrftoken = req.session._csrf;
  next();
})

然后,您的测试可能如下所示:

describe('Authenticated Jade tests', function () {
  this.timeout(5000);

  before(function (done) {
    [Set up an authenticated user here]
  });

  var validPaths = ['/help', '/products'];

  async.each(validPaths, function (path, callback) {
    it('should confirm that ' + path + ' serves HTML and is only available when logged in', function (done) {
      request.get('https://127.0.0.1:' + process.env.PORT + path, function (err, res, body) {
        expect(res.statusCode).to.be(302);
        expect(res.headers.location).to.be('/login');
        expect(body).to.be('Moved Temporarily. Redirecting to /login');

        var csrftoken = unescape(/XSRF-TOKEN=(.*?);/.exec(res.headers['set-cookie'])[1]);
        var authAttributes = { _csrf: csrftoken, email: userAttributes.email, password: 'password' };

        request.post('https://127.0.0.1:' + process.env.PORT + '/login', { body: authAttributes, json: true }, function (err, res) {
          expect(res.statusCode).to.be(303);

          request.get('https://127.0.0.1:' + process.env.PORT + path, function (err, res, body) {
            expect(res.statusCode).to.be(200);
            expect(body.toString().substr(-14)).to.be('</body></html>');

            request.get('https://127.0.0.1:' + process.env.PORT + '/bye', function () {
              done();
            });
          });
        });
      });
    });

    callback();
  });
});

这个想法是实际登录并使用发布你从cookie获得的CSRF令牌。请注意,您需要在mocha测试文件的顶部添加以下内容:

var request = require('request').defaults({jar: true, followRedirect: false});

答案 1 :(得分:1)

我所做的只是在非生产中公开csrf令牌:

if (process.env.NODE_ENV !== 'production') {
  app.use('/csrf', function (req, res, next) {
    res.json({
      csrf: req.csrfToken()
    })
  })
}

然后让它成为第一个测试并将其保存为全局测试。您必须在测试中使用代理,以便始终使用相同的会话。

答案 2 :(得分:1)

@dankohn出色的答案非常有帮助。关于supertest和csurf模块,事情已经发生了一些变化。因此,除了答案之外,我发现需要将以下内容传递给POST:

  it('should ...', function(done) {
    request(app)
      .get('/...')
      .expect(200)
      .end(function(err, res) {
        var csrfToken = unescape(/XSRF-TOKEN=(.*?);/.exec(res.headers['set-cookie'])[1]);
        assert(csrfToken);
        request(app)
          .post('/...')
          .set({cookie: res.headers['set-cookie']})
          .send({
            _csrf: csrfToken,
            ...
          })
          .expect(200)
          .end(done);
      });
  });