Skip to content

Commit

Permalink
added audience and issuer claim verification options
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanwalters committed Dec 20, 2015
1 parent 057db9b commit e06f5e2
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ The `'jwt'` scheme takes the following options:

* `secret` - __(required)__ _{string}_ secret key used to compute the signature.
* `algorithms` - __(optional)__ _{array}_ algorithm(s) allowed to verify tokens. Defaults to `['HS256']`. Valid algorithms: `['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'none']`
* `audience` - __(optional)__ _{string|integer}_ verify audience `(aud)` claim against this value
* `cookie` - __(optional)__ _{string}_ cookie name. Defaults to `sid`. Works in tandem with [`hapi-auth-cookie`](https://github.com/hapijs/hapi-auth-cookie).
Must set JWT when the cookie is set. See examples below.
* `issuer` - __(optional)__ _{string|integer}_ verify issuer `(iss)` claim against this value
* `token` - __(optional)__ _{string}_ name of the token set in the cookie. Defaults to `token`.
* `validateFunc` - __(optional)__ _{function}_ function to validate the decoded token on every request.

Expand Down
8 changes: 7 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ internals.algorithms = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'E
internals.optionsSchema = Joi.object({
secret: Joi.string().required().allow(''),
algorithms: Joi.array().items(Joi.string().valid(internals.algorithms)).default('HS256'),
audience: [Joi.string().alphanum(), Joi.number()],
cookie: Joi.string().default('sid'),
issuer: [Joi.string().alphanum(), Joi.number()],
token: Joi.string().default('token'),
validateFunc: Joi.func()
});
Expand All @@ -56,7 +58,11 @@ internals.implementation = (server, options) => {
return reply(Boom.unauthorized('No token found.'));
}

Jwt.verify(token, settings.secret, { algorithms: settings.algorithms }, (err, decoded) => {
Jwt.verify(token, settings.secret, {
algorithms: settings.algorithms,
audience: settings.audience,
issuer: settings.issuer
}, (err, decoded) => {

if (err) {
return reply(Boom.unauthorized(err));
Expand Down
170 changes: 170 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,176 @@ describe('Jot', () => {
});
});

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: 'jot',
secret: secret
});

const jwt = Jwt.sign({
scope: 'admin'
}, secret, {
issuer: 'not jot'
});

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 = 'jot';

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: 'jot',
secret: secret
});

const jwt = Jwt.sign({
scope: 'admin'
}, secret, {
audience: 'not jot'
});

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 = 'jot';

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();
Expand Down

0 comments on commit e06f5e2

Please sign in to comment.