Skip to content

Instantly share code, notes, and snippets.

@gregbown
Last active July 22, 2020 00:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gregbown/bdd4e7771573ca63df947bfeb5434654 to your computer and use it in GitHub Desktop.
Save gregbown/bdd4e7771573ca63df947bfeb5434654 to your computer and use it in GitHub Desktop.
Test login flow with CSRF token using Mocha, Chai, and Supertest
const assert = require('chai').assert;
const cookiejar = require('cookiejar');
const injector = require('../di-injector');
const testAccounts = require('../data/accounts.json');
const grabToken = new RegExp(/value=[a-zA-Z0-9\-_"]*(?= name="_csrf")/);
let agentFactory;
/** If you are using the tdd interface, suiteSetup maps to beforeAll, and setup maps to beforeEach. */
suite('routes', () => {
let sid;
/** suiteSetup maps to beforeAll */
suiteSetup(() => {
return Promise.all([injector('agentFactory')])
.then(([_agent]) => {
agentFactory = _agent;
}).catch(err => console.log('Error in setup:', err));
});
/** setup maps to beforeEach. */
// setup(() => {
// setup code here
// return console.log('All setup');
// });
test('Session persists and able to login with csrf token', (done) => {
let token;
agentFactory.guestAgent()
.then((agent) => {
return agent.get('/login').then((_res) => {
/*
* Grab the CSRF token from the markup in the login page
* <input type="hidden" value="JF9Mh3DU-VjeLztWWI9R6nNTBq777Wvl-mZg" name="_csrf">
* regex lookahead value=x followed by name="_csrf"
*/
token = _res.text.match(grabToken)[0].value.split('"')[1];
}).then(() => {
return agent.post('/login')
.set('Accept','application/json')
/* The secret ingredient for having the token validate is to persist the session cookie. Set method can also take an array of cookies */
.set('Cookie', agent.jar.getCookie('website.sid', new cookiejar.CookieAccessInfo('test.mydomain.com', '/')))
.send(Object.assign(testAccounts[0],{_csrf: `${token}`}))
.expect(302)
.expect((res) => {
assert.isNotNull(res.headers['set-cookie']);
assert.match(res.headers['set-cookie'], /website\.sid/);
assert.isTrue(res.redirect);
done();
})
.catch((err) => {
console.log('assertion error', err);
done()
});
})
.catch((err) => {
console.log('Login error', err);
done()
});
})
.catch((err) => {
console.log('agent error', err);
done()
});
});
/* This agent is already autorized much like the one above */
test('request secure page from logged in state', (done) => {
agentFactory.authAgent(testAccounts[0])
.then((agent) => {
return agent.get('/account')
.set('Cookie', agent.jar.getCookie('website.sid', new cookiejar.CookieAccessInfo('test.mydomain.com', '/')))
.expect(200)
.expect((res) => {
/* Start of web page */
assert.equal(res.text.charAt(0), '<');
done();
})
.catch((err) => {
console.log('assertion error', err);
done()
});
})
.catch((err) => {
console.log('agent error', err);
done()
});
});
test('request secure page with expired session', (done) => {
agentFactory.authAgent(testAccounts[0])
.then((agent) => {
return agent.get('/account')
.expect(401)
.expect((res) => {
/* Start of "Access denied" */
assert.equal(res.text.charAt(0), 'A');
done();
})
.catch((err) => {
console.log('assertion error', err);
done()
});
})
.catch((err) => {
console.log('agent error', err);
done()
});
});
});
@gregbown
Copy link
Author

gregbown commented Jul 22, 2020

A couple of notes here:
I am using test driven development and this is why the use of suite instead of describe
Ignore the setup part since it is probably unique to the dependency injection framework. The important part is getting the agent instance.

request = require('supertest');
const agent = request.agent(app);

I use cookiejar to extract the session cookie from supertests built in cookiejar and then use the built in set method to set the cookie on the each new request that requires a session.

Existing CSRF tokens will be invalidated if a new session is started so it is crucial to maintain the session cookie when testing the token. As you can see the last test I omit the setting of the cookie and expect a 401 Access Denied

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment