Search code examples
node.jsautomated-testscsrfchaichai-http

How to test http api that uses csurf csrf protection with mocha/chai?


Not a duplicate of this one

I want to know what are the minimum headers/cookies needed to set in order to test an api that uses csurf csrf protection.

In my server I have:

const app = express()

const csrfMiddleware = csurf({
    cookie: true
})

app.use(cookieParser())
app.use(csrfMiddleware)
app.get('/singup', ...)
app.post('/singup', ...)

In the test file:

const chai = require('chai')
const chaiHttp = require('chai-http')
chai.use(chaiHttp);
const request = chai.request;
const expect = chai.expect;

const cheerio = require('cheerio')
const app = "http://localhost:3000" 

function extractCsrfToken(res) {
    var $ = cheerio.load(res.text);
    return $('[name=_csrf]').val();
   }

describe('Post Endpoints', () => {
  it('should create a new user', () => {
        request(app).get('/singup').end((err, resGet)=>{
          expect(err).to.be.null;
          expect(resGet).to.have.status(200)
          const _csrf = extractCsrfToken(resGet);

          console.log(`sending request with csrf token ${_csrf}`)
          request(app)
          .post('/singup')
          .set('Cookie', `_csrf=${_csrf}`)
          .set('Connection', 'keep-alive')
          .set('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8')
          .send({
              username: "teste-automatizado",
              email: '[email protected]',
              password: '12345678',
              passwordConfirm: '12345678'
      
          }).end((err2, res)=>{
            expect(err2).to.be.null;
            expect(res).to.have.status(200)
          })
          
        })   
  })
})

What I expect is the user to be created and a 200 return from the POST request which is not happening because it's failing with 403 from the middleware:

$ npm test

> [email protected] test
> mocha



  Post Endpoints
    ✔ should create a new user


  1 passing (21ms)

sending request with csrf token PDwitHYN-ttgB5zAIGCQqq9cCxgwkbXyyrMM

/home/fabio/src/project/node_modules/mocha/lib/runner.js:962
    throw err;
    ^
AssertionError: expected { Object (_events, _eventsCount, ...) } to have status code 200 but got 403
    at /home/fabio/src/project/test.js:37:33
    at Test.Request.callback (/home/fabio/src/project/node_modules/superagent/lib/node/index.js:716:12)
    at IncomingMessage.<anonymous> (/home/fabio/src/project/node_modules/superagent/lib/node/index.js:916:18)
    at IncomingMessage.emit (node:events:402:35)
    at endReadableNT (node:internal/streams/readable:1343:12)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  showDiff: true,
  actual: 403,
  expected: 200
}

Solution

  • According to the documentation, the token is expected to be in a header named CSRF-Token.

    request(app)
      .post('/singup')
      .set('CSRF-Token', _csrf)
    ...
    

    Additionally, you probably need to set a Cookie header on your request. But you should use the value of the Set-Cookie header from the response of your first request.

    For that, chai has options to retain the cookies. The key is to use an agent to make all requests.

    const agent = chai.request.agent(app)
    
    agent.get('/singup').end((err, res) => {
      // do some tests and get the token
      // ...
      // the agent will use the cookie from the previous request
      // so you only need to set the CSRF-Token header 
      agent.post('/singup')
        .set('CSRF-Token', _csrf)
        .send({ username: 'me', password: '123' })
        .end((err, res) => {
           // handle the post response 
        })
    })
    
    agent.close()