【问题标题】:NodeJS Express Application Testing - How to submit CSRF token when testing with Mocha and Chai?NodeJS Express 应用测试 - 使用 Mocha 和 Chai 进行测试时如何提交 CSRF 令牌?
【发布时间】:2016-09-30 09:00:10
【问题描述】:

我正在尝试使用 Mocha、Chai 和 ChaiHttp 在我的 Express 应用程序中为 POST 路由编写测试,但由于我不断收到 HTTP 403 响应,我无法让它工作每当提交我的 CSRF 令牌时。以下是我到目前为止的代码:

express.js 配置文件

...
  app.use(session(config.SESSION));
  // csrf is the 'csurf' module
  app.use(csrf());

  app.use((req, res, next) => {
    res.cookie('XSRF-TOKEN', req.csrfToken());
    return next();
  });
...

User.test.js

'use strict';
process.env.NODE_ENV = 'test';

const User = require('../server/models/User');
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../index');
const utils = require('../utils');

chai.use(chaiHttp);

describe('Users', () => {
  beforeEach((done) => {
    User.remove({}, (err) => {
      done();
    });
  });

  after((done) => {
    server.close();
    done();
  });
...
 /*
   * [POST] /user
   */
  describe('[POST] /user', () => {
    it('should return a JSON object with a "success" property equal to "true" when creating a new user', (done) => {
      const userObj = utils.generateUserObject();

      chai.request(server)
          .get('/api')
          .end((error, response) => {

            userObj._csrf = utils.extractCsrfToken(response.headers['set-cookie']);


            /*
             * Accurately logs the _csrf property with the correct CSRF token that was retrieved via the initial GET request
             * 
             * Example output:
             * 
             * { 
             * username: 'Stacey89',
             * first_name: 'Gregg',
             * last_name: 'King',
             * ...
             * _csrf: 'vBhDfXUq-jE86hOHadDyjgpQOu-uE8FyUp_M' 
             * }
             *
             */ 


            console.log(userObj);

            chai.request(server)
                .post('/api/user')
                .set('content-type', 'application/x-www-form-urlencoded')
                .send(userObj)
                .end((err, res) => {
                  res.should.have.status(200);
                  res.body.should.be.a('object');
                  res.body.should.have.property('success').eql('true');
                  done();
                });
          });
    });
...

utils.js

...
  extractCsrfToken(cookiesObj) {
    const cookiesArray = Array.prototype.join.call(cookiesObj, '').split(';');
    let csrfToken = 'NOT FOUND';

    cookiesArray.forEach((cookie) => {
      if (cookie.includes('XSRF-TOKEN')) {
        csrfToken = cookie.split('=').splice(1, 1).join('');
      }
    });

    return csrfToken;
  }
...

当我运行上述测试时,我收到以下错误:

ForbiddenError: invalid csrf token
...
POST /api/user 403

奇怪的是,如果我使用与前面描述的完全相同的配置从 Postman 发出 POST 请求,我成功地得到了我正在寻找的响应并且表单提交成功。

仅当从我的测试套件提交 userObj 时,它似乎不起作用。

更新 #1 我终于设法为我的问题找到了一个可行的解决方案。

我已将之前设置 XSRF-TOKEN cookie 的中间件更新为以下内容:

  app.use((err, req, res, next) => {
    res.locals._csrf = req.csrfToken();
    return next();
  });

现在单元测试运行成功。

此外,我注意到向服务器发出的第一个[GET] 请求返回了Set-Cookie 标头:

Status Code: 200 OK
Content-Length: 264
Content-Type: application/json; charset=utf-8
Date: Tue, 18 Oct 2016 12:10:09 GMT
Etag: W/"108-NSQ2HIdRqiuMIf0F+7qwjw"
Set-Cookie: connect.sid=s%3Ap5io8_3iD7Wy0X0K77qWZLoYj-fD1ZbA.6uvcBiB%2B%2BSi1KOVOmJgvWe%2B5Mqpuc1rs9yUYxH0uNPY; Path=/; HttpOnly
X-Download-Options: noopen
X-XSS-Protection: 1; mode=block
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-frame-options: SAMEORIGIN

任何后续的[GET] 请求都不会返回该标头:

Status Code: 200 OK
Content-Length: 264
Content-Type: application/json; charset=utf-8
Date: Tue, 18 Oct 2016 12:11:19 GMT
Etag: W/"108-NSQ2HIdRqiuMIf0F+7qwjw"
X-Download-Options: noopen
X-XSS-Protection: 1; mode=block
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-frame-options: SAMEORIGIN

这在应用程序安全方面可以吗?简单地在res.locals 对象上设置_csrf 标记是一种好习惯吗?

【问题讨论】:

    标签: node.js express testing mocha.js chai


    【解决方案1】:

    chai-http 的 send() 方法用于发送 json(通常需要使用 json 正文解析器)。因此,您也不应该将内容类型设置为 application/x-www-form-urlencoded

    如果您没有使用 json 正文解析器并且您确实想将其作为表单数据发送,field() method 应该可以工作:

    chai.request(server)
        .post('/api/user')
        .field('_csrf', _csrf)
        .end((err, res) => {
            res.should.have.status(200);
            res.body.should.be.a('object');
            res.body.should.have.property('success').eql('true');
            done();
        });
    

    【讨论】:

    • 我不想通过 cookie 发送它 - 我将 _csrf 令牌作为 req.body 的一部分发送
    • _csrf 定义在哪里?
    猜你喜欢
    • 2017-02-18
    • 1970-01-01
    • 2016-11-15
    • 2016-09-20
    • 1970-01-01
    • 2017-10-08
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多