Skip to content

Commit

Permalink
heroku example works. just need to get test coverage up by adding a f…
Browse files Browse the repository at this point in the history
…ew more tests #9
  • Loading branch information
nelsonic committed Apr 19, 2015
1 parent 26a4c0e commit 4332ca1
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_js:
- 0.10
- 0.12
- iojs
env:
- JWT_SECRET="EverythingIsAwesome!"
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ If you have ***any questions*** on this please post an issue/question on GitHub:
https://github.com/ideaq/hapi-auth-jwt2/issues
(*we are here to help get you started on your journey to **hapi**ness!*)

#### Production-ready Example using Redis?


Redis is *perfect* for storing session data that needs to be checked
on every authenticated request.
If you are unfamiliar with Redis or need a refresher,
please see: https://github.com/docdis/learn-redis

Feature request *seconded* by [@manonthemat](https://github.com/manonthemat) see:
[hapi-auth-jwt2/issues/9](https://github.com/ideaq/hapi-auth-jwt2/issues/9)


## Documentation

- `validateFunc` - (***required***) a the function which is run once the Token has been decoded
Expand Down
125 changes: 125 additions & 0 deletions example/real_world_example_using_redis_on_heroku.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
var Hapi = require('hapi');
var hapiAuthJWT = require('../lib/'); // require('hapi-auth-jwt2');
var JWT = require('jsonwebtoken'); // used to sign our content
var port = process.env.PORT || 8000; // allow port to be set
var aguid = require('aguid')
var redis = require('redis');
var url = require('url');
// if you don't already have a *FREE* RedisCloud Account
// visit: https://addons.heroku.com/rediscloud (it works outside heroku! free!)
var redisURL = url.parse(process.env.REDISCLOUD_URL);
var redisClient = redis.createClient(redisURL.port, redisURL.hostname,
{no_ready_check: true});
redisClient.auth(redisURL.auth.split(":")[1]);

redisClient.set('redis', 'working');
redisClient.get('redis', function (err, reply) {
console.log('redis is ' +reply.toString()); // confirm we can access redis
});

// bring your own validation function
var validate = function (decoded, request, callback) {
console.log(" - - - - - - - DECODED token:");
console.log(decoded);
// do your checks to see if the session is valid
redisClient.get(decoded.id, function (rediserr, reply) {
console.log(' - - - - - - - REDIS reply - - - - - - - ', reply);
if(reply) {
try {
var session = JSON.parse(reply);
} catch(e) {
return callback(null, false);
}
}
else { // unable to find session in redis ... reply is null
return callback(null, false);
}

if (session.valid === true) {
return callback(null, true);
}
else {
return callback(null, false);
}
});
};

var server = new Hapi.Server();
server.connection({ port: port });

server.register(hapiAuthJWT, function (err) {
// if(err) { // uncomment this in prod
// console.log(err);
// }
// see: http://hapijs.com/api#serverauthschemename-scheme
server.auth.strategy('jwt', 'jwt', true,
{ key: process.env.JWT_SECRET, validateFunc: validate,
verifyOptions: { ignoreExpiration: true }
});

server.route([
{
method: "GET", path: "/", config: { auth: false },
handler: function(request, reply) {
reply({text: 'Token not required'});
}
},
{
method: ['GET','POST'], path: '/restricted', config: { auth: 'jwt' },
handler: function(request, reply) {
reply({text: 'You used a Token!'})
.header("Authorization", request.headers.authorization);
}
},
{
method: ['GET','POST'], path: "/auth", config: { auth: false },
handler: function(request, reply) {
// implement your own login/auth function here
var session = {
valid: true, // this could be set to false if the person logs out
id: aguid(), // a random session id
exp: new Date().getTime() + 30 * 60 * 1000 // expires in 30 minutes time
}
// create the session in Redis
redisClient.set(session.id, JSON.stringify(session));
// sign the session as a JWT
var token = JWT.sign(session, process.env.JWT_SECRET); // synchronous
console.log(token);

reply({text: 'Check Auth Header for your Token'})
.header("Authorization", token);
}
},
{
method: ['GET','POST'], path: "/logout", config: { auth: 'jwt' },
handler: function(request, reply) {
// implement your own login/auth function here
var decoded = JWT.decode(request.headers.authorization,
process.env.JWT_SECRET);
redisClient.get(decoded.id, function(rediserror, reply) {
var session = JSON.parse(reply)
console.log(' - - - - - - SESSION - - - - - - - -')
console.log(session);
// update the session to no longer valid:
session.valid = false;
session.ended = new Date().getTime();
// create the session in Redis
redisClient.set(session.id, JSON.stringify(session));
reply({text: 'Successfully Logged Out! (Session no longer valid)'})
.header("Authorization", request.headers.authorization);
})
}
},
{ // remove this method if you use this in PROD!
method: 'GET', path: '/end', config: { auth: false },
handler: function(request, reply) {
redisClient.end();
reply({text: 'end'});
}
}
]);
});

// server.start(); // uncomment this to run the server directly
// console.log('Now Visit: http://127.0.0.1:'+port);
module.exports = server;
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
},
"homepage": "https://github.com/ideaq/hapi-auth-jwt",
"dependencies": {
"aguid": "^1.0.3",
"boom": "^2.7.0",
"hoek": "^2.12.0",
"jsonwebtoken": "^5.0.0"
"jsonwebtoken": "^5.0.0",
"redis": "^0.12.1"
},
"peerDependencies": {
"hapi": ">=8.x.x"
Expand All @@ -46,7 +48,8 @@
"coverage": "istanbul cover ./node_modules/tape/bin/tape ./test/*.js && ./node_modules/.bin/istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100",
"jshint": "./node_modules/jshint/bin/jshint -c .jshintrc --exclude-path .gitignore .",
"codeclimate": "CODECLIMATE_REPO_TOKEN=1f00e2891b3e24ed11bb9b22a8a4793c55c5470d60e859389a0c3dd24220df46 ./node_modules/codeclimate-test-reporter/bin/codeclimate.js < ./coverage/lcov.info",
"start":"node example/server.js"
"start": "node example/server.js",
"report":"open coverage/lcov-report/index.html"
},
"pre-commit": [
"jshint",
Expand Down
178 changes: 178 additions & 0 deletions test/real_world_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
var test = require('tape');
var JWT = require('jsonwebtoken');

var server = require('../example/real_world_example_using_redis_on_heroku.js');

test("Confirm GET / does not require session token", function(t) {
var options = {
method: "GET",
url: "/"
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 200, "Base URL Does not Require ");
t.end();
});
});

test("Attempt to access restricted content (without auth token)", function(t) {
var options = {
method: "POST",
url: "/restricted"
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 401, "No Token should fail");
t.end();
});
});


test("Attempt to access restricted content (with an INVALID Token)", function(t) {
var options = {
method: "POST",
url: "/restricted",
headers: { authorization: "Bearer fails.validation" }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 401, "INVALID Token should fail");
t.end();
});
});

test("Malformed JWT", function(t) {
// use the token as the 'authorization' header in requests
// var token = jwt.sign({ "id": 1 ,"name":"Old Greg" }, 'incorrectSecret');
// console.log(token);
var options = {
method: "POST",
url: "/restricted",
headers: { authorization: "Bearer my.invalid.token" }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
console.log(response.result);
console.log(' '); // blank line
t.equal(response.statusCode, 401, "INVALID Token should fail");
// t.equal(JSON.parse(response.result).msg, 'Invalid Token', "INVALID Token should fail");
t.end();
});
});

test("Try using an incorrect secret to sign the JWT", function(t) {
// use the token as the 'authorization' header in requests
var token = JWT.sign({ id:123,"name":"Charlie" }, 'incorrectSecret');
console.log(" - - - - - - token - - - - -")
console.log(token);
var options = {
method: "POST",
url: "/restricted",
headers: { authorization: "Bearer "+token }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 401, "Token signed with incorrect key fails");
t.equal(true, true, "true is true")
t.end();
});
});

test("Token is well formed but session not in redis so should be denied", function(t) {
// use the token as the 'authorization' header in requests
// var token = jwt.sign({ "id": 1 ,"name":"Old Greg" }, 'incorrectSecret');
var token = JWT.sign({ id:321,"valid":true }, process.env.JWT_SECRET);
var options = {
method: "POST",
url: "/restricted",
headers: { authorization: "Bearer "+token }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 401, "Denied");
t.end();
});
});

test("Attempt to access restricted content with Well-formed Token but invalid secret", function(t) {
// use the token as the 'authorization' header in requests
var token = JWT.sign({ id:123, "valid":true }, 'badsecret');
var options = {
method: "POST",
url: "/restricted",
headers: { authorization: "Bearer "+token }
};
// server.inject lets us similate an http request
server.inject(options, function(response) {
console.log(" - - - - RESPONSE: ")
console.log(response.result);
t.equal(response.statusCode, 401, "InVALID Token should Error!");

t.end();
});
});

var token; // used in all subsequent tests

test("Simulate Authentication", function(t) {
// use the token as the 'authorization' header in requests
var options = {
method: "POST",
url: "/auth"
};
// server.inject lets us similate an http request
server.inject(options, function(res) {
token = res.headers.authorization;
console.log(" - - - - RESPONSE: ");
console.log(res.result);
t.equal(res.statusCode, 200, "VALID Token should succeed!");

t.end();
});
});

test("Access restricted content with *VALID* JWT", function(t) {
// use the token as the 'authorization' header in requests
var options = {
method: 'POST',
url: '/restricted',
headers: { 'Authorization' : token } // token from previous test
};
// server.inject lets us similate an http request
server.inject(options, function(res) {
token = res.headers.authorization;
console.log(" - - - - RESPONSE: ");
console.log(res.result);
t.equal(res.statusCode, 200, "VALID Token should succeed!");
t.end();
});
});

test("Attempt to Access restricted content with JWT which is no longer valid", function(t) {
// use the token as the 'authorization' header in requests
var options = {
method: 'POST',
url: '/restricted',
headers: { 'Authorization' : token } // token from previous test
};
// server.inject lets us similate an http request
server.inject(options, function(res) {
token = res.headers.authorization;
console.log(" - - - - RESPONSE: ");
console.log(res.result);
t.equal(res.statusCode, 200, "VALID Token should succeed!");
t.end();
});
});

test("End Connection to RedisCloud", function(t) {
var options = {
method: "GET",
url: "/end"
};
server.inject(options, function(response) {
t.equal(response.statusCode, 200, "Redis connection closed.");
server.stop();
t.end();
});
});

0 comments on commit 4332ca1

Please sign in to comment.