Brief
The new buzz word is JWT(JSON Web Token)[I know, is not so new now :( ]. Well, this cool approach for send and consume an information easily with an encode for "security".
So, Hapi have an awesome auth system where you can create a lot of auth types, for you use when is required for that case, for example, internally you can use a JWT and for external connection an Hawk, Basic Auth, or an OAuth auth. You can have one for case you need, but in this case we'll talk about JWT auth.
The Road
I really don't want to entry in the internals about the auth system in Hapi, but show how you can implementing Hapi + JWT for authenticate your users and distribute a token for retrieve information.
My favorite plugin for that is hapi-auth-jwt2. You maybe are asking: "Why jwt2?" this is simple because "hapi-auth-jwt" was taken :p
'use strict'; | |
const Promise = require('bluebird'); | |
const jwt = require('hapi-auth-jwt2'); | |
const db = require('./database'); | |
exports.register = (server, options, next) => { | |
server.register(jwt, registerAuth); | |
function registerAuth (err) { | |
if (err) { return next(err); } | |
server.auth.strategy('jwt', 'jwt', { | |
key: process.env.JWT || 'stubJWT', | |
validateFunc: validate, | |
verifyOptions: {algorithms: [ 'HS256' ]} | |
}); | |
server.auth.default('jwt'); | |
return next(); | |
} | |
function validate (decoded, request, cb) { | |
const User = db.User; | |
return new Promise((resolve) => { | |
User.findAsync({_id: decoded.id}) | |
.then((user) => { | |
if (!user) { | |
return cb(null, false); | |
} | |
return cb(null, true); | |
}); | |
}); | |
} | |
}; | |
exports.register.attributes = { | |
name: 'auth-jwt', | |
version: '1.0.0' | |
}; |
This is my implementation of hapi-auth-jwt2, this code consist in two "big" functions.
registerAuth is the registry function in Hapi, that configuring the plugin with your settings.
You must passing an object with key value to be an "secretKey" for your JWT validating if the token is trusted.
validateFunc is a function that is called every time a request is received and you business rules validate if the token is valid.
verifyOptions is an Object with some options for JWT internals, but I only use the algorithms for setting it, you can see more here.
The second "big" function is the validate function. This function is used in validateFunc.
In that function, we receive an decoded JWT and a request interface and a callback.
My case is so simple, my JWT is the ID of the user in database, so just find the user's ID and if the ID is valid, we can continue or not, YOU SHALL NOT PASS
So Marcos, you don't show me how I can generate the JWT to send for my clients. Just see bellow Padawan.
'use strict'; | |
const jwt = require('jsonwebtoken'); | |
const Boom = require('boom'); | |
function UserController (db) { | |
this.database = db; | |
this.model = db.User; | |
} | |
module.exports = UserController; | |
UserController.prototype.create = function (request, reply) { | |
let payload = request.payload; | |
this.model.createAsync(payload) | |
.then((user) => { | |
let token = getToken(user.id); | |
reply({ | |
token: token | |
}).code(201); | |
}) | |
.catch((err) => { | |
reply(Boom.badImplementation(err.message)); | |
}); | |
}; | |
UserController.prototype.logIn = function (request, reply) { | |
let credentials = request.payload; | |
this.model.findOneAsync({email: credentials.email}) | |
.then((user) => { | |
if (!user) { | |
return reply(Boom.unauthorized('Email or Password invalid')); | |
} | |
if (!user.validatePassword(credentials.password)) { | |
return reply(Boom.unauthorized('Email or Password invalid')); | |
} | |
let token = getToken(user.id); | |
reply({ | |
token: token | |
}); | |
}) | |
.catch((err) => { | |
reply(Boom.badImplementation(err.message)); | |
}); | |
}; | |
function getToken (id) { | |
let secretKey = process.env.JWT || 'stubJWT'; | |
return jwt.sign({ | |
id: id | |
}, secretKey, {expiresIn: '18h'}); | |
} |
When a user is created or just logged in, we send the token for our users.
Because when you create your account is damn useful(and cool) when the site redirect you already logged(IMO).
And in my function getToken I just use the module jsonwebtoken for sign the token with the ID.
If you want to use scopes for require a specific role for this route, you can pass in JWT an array with the user's roles.(In a soon post, I'll talk about it -)
getToken, set the secretKey, equal than our previously secretKey in our auth plugin above. (If you specify a different secretKey, the plugin cannot trust your token)
In jwt.sign function, you need pass the object that you want to encode in a JWT, your secretKey and options for your JWT. In my case I just set expiresIn with 18 hours.
And thats all folks. Simple and Cool.
You can see this authenticate schema implemented in my boilerplate project called start-hapiness.
Tricks
If you want to use JWT please, never, I said NEVER send sensitive informations here, because JWT is not encrypted, is just encoded you can encrypt it with a RSA key stored the User's model or something like that, but NEVER send passwords or other sensitive informations.
Because JWT is just encoded, if you've an token, you can see the content easily like this:
let token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ'
atob(token.split('.')[1]) // see your content here =D
This token is extracted from JWT's site, you can see the real content there.
Thanks folks
I need to say Thank you folks for seeing, replying and recommend my post. It's gratifying to know that I’m helping someone with my posts.
Again, THANKS.