jot
Version:
hapi JSON Web Token (JWT) authentication plugin
771 lines (554 loc) • 20.7 kB
JavaScript
// Load modules
const Boom = require('boom');
const Code = require('code');
const Hapi = require('hapi');
const Jwt = require('jsonwebtoken');
const Lab = require('lab');
// Test shortcuts
const lab = exports.lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const expect = Code.expect;
describe('Jot', () => {
it('fails initialization with no options', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
expect(() => {
server.auth.strategy('jwt', 'jwt');
}).to.throw(Error);
done();
});
});
it('fails initialization with no secret defined', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
expect(() => {
server.auth.strategy('jwt', 'jwt', {
cookie: 'test',
token: 'fancyToken'
});
}).to.throw(Error);
done();
});
});
it('fails initialization with invalid options', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
expect(() => {
server.auth.strategy('jwt', 'jwt', {
secret: { foo: 'bar' },
cookie: 123,
token: () => { }
});
}).to.throw(Error);
done();
});
});
it('initializes with secret defined', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
expect(() => {
server.auth.strategy('jwt', 'jwt', {
secret: 'SuperSecret!'
});
}).to.not.throw();
done();
});
});
it('authenticates a request using the "Authorization" header', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
secret: secret
});
const jwt = Jwt.sign({
aud: 'user'
}, secret);
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(true);
expect(res.statusCode).to.equal(200);
done();
});
});
});
it('authenticates a request token is stored in a cookie', (done) => {
const server = new Hapi.Server();
server.connection();
server.register([require('../'), require('hapi-auth-cookie')], (err) => {
expect(err).to.not.exist();
const cookieName = 'cookie';
const secret = 'JwtSecret!';
server.auth.strategy('jwt', 'jwt', {
cookie: cookieName,
secret: secret
});
server.auth.strategy('session', 'cookie', {
cookie: cookieName,
password: 'HapiNowRequiresPasswordsAtLeast32Chars'
});
server.route([{
method: 'GET', path: '/login',
config: {
auth: {
mode: 'try',
strategy: 'session'
},
handler: (request, reply) => {
request.cookieAuth.set({
token: Jwt.sign({
aud: 'user'
}, secret)
});
return reply('ok');
}
}
}, {
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
}]);
server.inject({ method: 'GET', url: '/login' }, (res) => {
expect(res.statusCode).to.equal(200);
const header = res.headers['set-cookie'];
expect(header.length).to.equal(1);
const cookie = header[0].match(/(?:[^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:([^\x00-\x20\"\,\;\\\x7F]*))/);
server.inject({ method: 'GET', url: '/secure', headers: { cookie: cookie[0] } }, (res2) => {
expect(res2.request.auth.isAuthenticated).to.equal(true);
expect(res2.statusCode).to.equal(200);
done();
});
});
});
});
it('fails authentication when no token is passed', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
server.auth.strategy('jwt', 'jwt', {
secret: 'SuperSecret!'
});
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
server.inject({ method: 'GET', url: '/secure' }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
});
});
it('fails authentication when signatures do not match', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
server.auth.strategy('jwt', 'jwt', {
secret: 'ValidSecret!'
});
const jwt = Jwt.sign({
aud: 'user'
}, 'HackerSecret!');
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
});
});
it('fails authentication when token has expired', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
secret: secret
});
const jwt = Jwt.sign({
aud: 'AwesomeUser'
}, secret, {
expiresIn: -1000
});
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
setTimeout(() => {
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
}, 1000);
});
});
it('fails authentication when wrong algorithm is used to sign the token', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
algorithms: ['HS256'],
secret: secret
});
const jwt = Jwt.sign({
aud: 'AwesomeUser'
}, secret, {
algorithm: 'HS384'
});
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
setTimeout(() => {
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
}, 1000);
});
});
it('fails authentication when current time is before the not-before time', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
secret: secret
});
const jwt = Jwt.sign({
aud: 'AwesomeUser',
nbf: (+new Date(2100, 1, 1) / 1000)
}, secret);
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
setTimeout(() => {
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
}, 1000);
});
});
it('fails authentication when issuer claim fails verification', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
issuer: '123',
secret: secret
});
const jwt = Jwt.sign({
scope: 'admin'
}, secret, {
issuer: 'something else'
});
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
setTimeout(() => {
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
}, 1000);
});
});
it('authenticates when issuer claim is valid', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
const issuer = 'github.com';
server.auth.strategy('jwt', 'jwt', {
issuer: issuer,
secret: secret
});
const jwt = Jwt.sign({
scope: 'admin'
}, secret, {
issuer: issuer
});
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
setTimeout(() => {
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(true);
expect(res.statusCode).to.equal(200);
done();
});
}, 1000);
});
});
it('fails authentication when audience claim fails verification', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
audience: 'github.com',
secret: secret
});
const jwt = Jwt.sign({
scope: 'admin'
}, secret, {
audience: 'google.com'
});
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
setTimeout(() => {
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
}, 1000);
});
});
it('authenticates when issuer audience is valid', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
const audience = 'github.com';
server.auth.strategy('jwt', 'jwt', {
audience: audience,
secret: secret
});
const jwt = Jwt.sign({
scope: 'admin'
}, secret, {
audience: audience
});
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
setTimeout(() => {
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(true);
expect(res.statusCode).to.equal(200);
done();
});
}, 1000);
});
});
it('fails authentication when validation function has an error', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
secret: secret,
validateFunc: (request, token, callback) => {
const error = Boom.badRequest('wah wah');
callback(error, false);
}
});
const jwt = Jwt.sign({
aud: 'user'
}, secret);
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
});
});
it('fails authentication when token is invalid', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
secret: secret,
validateFunc: (request, token, callback) => {
return callback(null, false);
}
});
const jwt = Jwt.sign({
aud: 'user'
}, secret);
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(false);
expect(res.statusCode).to.equal(401);
done();
});
});
});
it('authenticates when token is valid', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
secret: secret,
validateFunc: (request, token, callback) => {
return callback(null, true);
}
});
const jwt = Jwt.sign({
aud: 'user'
}, secret);
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(true);
expect(res.statusCode).to.equal(200);
done();
});
});
});
it('authenticates when token is valid and credentials are updated', (done) => {
const server = new Hapi.Server();
server.connection();
server.register(require('../'), (err) => {
expect(err).to.not.exist();
const secret = 'SuperSecret!';
server.auth.strategy('jwt', 'jwt', {
secret: secret,
validateFunc: (request, token, callback) => {
return callback(null, true, { aud: 'updatedUser' });
}
});
const jwt = Jwt.sign({
aud: 'user'
}, secret);
server.route({
method: 'GET', path: '/secure',
config: {
auth: 'jwt',
handler: (request, reply) => {
return reply('ok');
}
}
});
server.inject({ method: 'GET', url: '/secure', headers: { 'Authorization': jwt } }, (res) => {
expect(res.request.auth.isAuthenticated).to.equal(true);
expect(res.statusCode).to.equal(200);
done();
});
});
});
});
;