Skip to content

Commit

Permalink
adds ability to bypass JWT.verify as discussed in #130
Browse files Browse the repository at this point in the history
  • Loading branch information
nelsonic committed Dec 18, 2015
1 parent a1bc2f2 commit d44bf4b
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 43 deletions.
24 changes: 15 additions & 9 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,45 @@ internals.isFunction = function (functionToCheck) {

internals.implementation = function (server, options) {
assert(options, 'options are required for jwt auth scheme'); // pre-auth checks
assert(options.key, 'options must contain secret key or key lookup function'); // no signing key
assert(typeof options.validateFunc === 'function', 'options.validateFunc must be a valid function');

return {
authenticate: function (request, reply) {
var token = extract(request, options);

var token = extract(request, options); // extract token from Header, Cookie or Query param

if (!token) {
return reply(Boom.unauthorized(null, 'Token'));
}

if (!extract.isValid(token)) {
if (!extract.isValid(token)) { // quick check for validity of token format
return reply(Boom.unauthorized('Invalid token format', 'Token'));
}
} // verification is done later, but we want to avoid decoding if malformed

// if the keyFunc is a function it allows dynamic key lookup see: https://github.com/dwyl/hapi-auth-jwt2/issues/16
var keyFunc = (internals.isFunction(options.key)) ? options.key : function (decoded, callback) { callback(null, options.key); };

// otherwise use the same key (String) to validat all JWTs
var decoded;
try {
decoded = JWT.decode(token);
decoded = JWT.decode(token); // decode is non-io and fast enough to not have to be async
}
catch(e)
{
catch(e) { // request should still FAIL if the token does not decode.
return reply(Boom.unauthorized('Invalid token format', 'Token'));
}

if(options.bypass_verifcation) { // see: https://github.com/dwyl/hapi-auth-jwt2/issues/130
return reply.continue({ credentials: decoded, artifacts: token });
} // make token available in request.auth.credentials but skip JWT.verify

keyFunc(decoded, function (err, key, extraInfo) {
if (err) {
return reply(Boom.wrap(err));
}
if (extraInfo) {
request.plugins[pkg.name] = { extraInfo: extraInfo };
}
// additional checks
assert(options.key, 'options must contain secret key or key lookup function'); // no signing key
assert(typeof options.validateFunc === 'function', 'options.validateFunc must be a valid function');
var verifyOptions = options.verifyOptions || {};
JWT.verify(token, key, verifyOptions, function (err, decoded) {
if (err && err.name === 'TokenExpiredError') {
Expand Down
34 changes: 6 additions & 28 deletions test/verify_bypass_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,25 @@ var secret = 'NeverShareYourSecret';
var server = new Hapi.Server({ debug: { 'request': ['error', 'uncaught'] } });
server.connection();

// defining our own validate function lets us do something
// useful/custom with the decodedToken before reply(ing)
var validate = function (decoded, request, callback) {
// don't need to add anything here
};

// see discussion in https://github.com/dwyl/hapi-auth-jwt2/issues/130
// var bypass_validation = function(decoded, callback) {
// console.log(' - - - - - - - - - - - - - - - - > hello');
// console.log(decoded);
// // console.log(req);
// // can we simply short-circuit the verification?
// return reply.continue({ credentials: decoded});
// }

var sendToken = function(req, reply) {
return reply(req.auth.token);
};

var home = function(req, reply) {
return reply('Hai!');
};

var privado = function(req, reply) {
return reply('worked');

return reply(req.auth.credentials);
};

server.register(require('../'), function () {

server.auth.strategy('jwt', 'jwt', {
key: 'bypass_validation',
validateFunc: validate,
verifyOptions: { algorithms: [ 'HS256' ] } // only allow HS256 algorithm
bypass_verifcation: true // no validateFunc or key required.
});

server.route([
{ method: 'GET', path: '/', handler: home, config: { auth: false } },
{ method: 'GET', path: '/token', handler: sendToken, config: { auth: 'jwt' } },
{ method: 'POST', path: '/privado', handler: privado, config: { auth: 'jwt' } },
{ method: 'POST', path: '/required', handler: privado, config: { auth: { mode: 'required', strategy: 'jwt' } } },
{ method: 'POST', path: '/optional', handler: privado, config: { auth: { mode: 'optional', strategy: 'jwt' } } },
{ method: 'GET', path: '/', handler: sendToken, config: { auth: false } },
{ method: 'GET', path: '/required', handler: privado, config: { auth: { mode: 'required', strategy: 'jwt' } } },
{ method: 'GET', path: '/optional', handler: privado, config: { auth: { mode: 'optional', strategy: 'jwt' } } },
{ method: 'GET', path: '/try', handler: privado, config: { auth: { mode: 'try', strategy: 'jwt' } } }
]);

Expand Down
46 changes: 40 additions & 6 deletions test/verify_bypass_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,57 @@ test("Access a route that has no auth strategy", function(t) {
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 200, "GET / works without token");

t.equal(response.statusCode, 200, "GET / still works without token.");
t.end();
});
});

test("Access route configured in 'try' mode ", function(t) {
var token = JWT.sign({ id: 123, "name": "Charlie" }, 'NoSecret');
test("Bypass Verification in 'try' mode ", function(t) {
var payload = { id: 123, "name": "Charlie" }
var token = JWT.sign(payload, 'SecretDoesNOTGetVerified');
var options = {
method: "GET",
url: "/try",
headers: { authorization: "Bearer " + token }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
console.log(response.result);
t.equal(response.statusCode, 200, "GET /try should pass");
t.equal(response.result.id, payload.id, 'Decoded JWT returned by handler');
t.equal(response.statusCode, 200, "GET /try bypasses verification");
t.end();
});
});

test("Bypass Verification in 'optional' mode ", function(t) {
var payload = { id: 234, "name": "Oscar" }
var token = JWT.sign(payload, 'SecretDoesNOTGetVerified');
var options = {
method: "GET",
url: "/optional",
headers: { authorization: "Bearer " + token }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
t.equal(response.result.id, payload.id, 'Decoded JWT returned by handler');
t.equal(response.statusCode, 200, "GET /optional bypasses verification");
t.end();
});
});

test("Bypass Verification in 'required' mode ", function(t) {
var payload = { id: 345, "name": "Romeo" }
var token = JWT.sign(payload, 'AnyStringWillDo');
var options = {
method: "GET",
url: "/required",
headers: { authorization: "Bearer " + token }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
// console.log(response.result);
var credentials = JSON.parse(JSON.stringify(response.result));
t.equal(credentials.id, payload.id, 'Decoded JWT is available in handler');
t.equal(response.statusCode, 200, "GET /required bypasses verification");
t.end();
});
});

0 comments on commit d44bf4b

Please sign in to comment.