Authentication and Authorization with hapi.

Securing your node application.

Node.js has been my favorite system to work with for the past couple of years. It’s extremely fast, there is an amazing ecosystem of code, developers have found creative ways to solve new and old problems, and the community is very supportive. Every day modules are evolving and new ones are being created.

During the time I have spent with node, my favorite module and web server has become hapi.js. It has really solved a lot of problems in a clean way that other node web servers haven’t done yet, and with a focus on security first. Built into hapi you have access to route validation, api documentation, logging, modularity with a plugin architecture, caching, authentication, and authorization.

We are going to focus on those last two items, authentication and authorization. A lot of people don’t realize these two items mean completely different things but they are both equally important and are required for a secure application. Authentication is about validating that a user really is who they claim to be and authorization is about the permissions of the user and what they have access to.

For authentication in a hapi server application we use a strategy based method. We setup a strategy from a predefined scheme by giving it a name and passing in our configuration options. We will be using the hapi-auth-cookie scheme for our strategy but there are other strategies that you can easily use (or create your own!).

server.auth.strategy(‘base’, ‘cookie’, {
password: ‘supersecretpassword’, // cookie secret
cookie: ‘app-cookie’, // Cookie name
ttl: 24 * 60 * 60 * 1000 // Set session to 1 day
});

Now that we have our authentication strategy setup on our server we can use it on any of our routes. Lets add a login route that sets the user’s session with the ‘base’ authentication strategy. Also we are going to use Joi to validate that our incoming payload is an email and password.

NOTE: Validating incoming data improves the strength and security of our server. Also, hapi can take the route validation and generate documentation from it!

server.route({
method: ‘POST’,
path: ‘/login’,
config: {
validate: {
payload: {
email: Joi.string().email().required(),
password: Joi.string().min(2).max(200).required()
}
},
handler: function(request, reply) {
      getValidatedUser(request.payload.email, request.payload.password)
.then(function(user){
if (user) {
request.auth.session.set(user);
return reply(‘Login Successful!’);
} else {
return reply(Boom.unauthorized(‘Bad email or password’));
}
})
.catch(function(err){
return reply(Boom.badImplementation());
});
}
}
});

Inside our route handler we are using a simple method that returns a promise to get and validate the user’s email and password. So if the user is found and the password is valid, we then set the user’s session with request.auth.session.set(user).

{id: 123, email: ‘guest@guest.com’}

Setting the user’s session actually takes the user object above, encrypts it with the super secret password you passed into the scheme using iron, adds the session token to memory, and sets that encrypted json object as the cookie using HttpOnly. By setting the cookie to HttpOnly the client does not have access to the cookie but the server still does. This makes this authentication method even more secure since client side browser scripts don’t have access to the cookie. You don’t want scripts sharing your user’s session token!

It’s just as easy to clear a user’s session as it is to set. Here is a logout route as an example.

server.route({
method: ‘GET’,
path: ‘/logout’,
config: {
handler: function(request, reply) {
request.auth.session.clear();
return reply(‘Logout Successful!’);
}
}
});

Now to setup secure routes we just need to pass into the config an auth object that contains the strategy we want to use. In this case we are going to be using the base authentication strategy. For a user to access this route they would have to have their session set from the login route.

server.route({
method: ‘GET’,
path: ‘/example’,
config: {
auth: {
strategy: ‘base’
},
handler: function(request, reply) {
return reply(‘Success, you can access a secure route!’);
}
}
});

Every time a user hits this route hapi will decrypt and validate the user’s session cookie all based on the ‘base’ strategy.

There are a few extras that you have access to using a strategy. We can see if a user is logged in with request.auth.isAuthenticated and get access to the decrypted session user object with request.auth.credentials.

To make our server a little more secure lets set the default auth object on all routes to have the base strategy. This will prevent us from accidentally creating a route that is not secure.

server.auth.default({
strategy: ‘base’
});

So now if you don’t want a secure route you will have to declare false for auth.

server.route({
method: ‘GET’,
path: ‘/another-example’,
config: {
auth: false,
handler: function(request, reply) {
return reply(‘Success, You have accessed a public route!’);
}
}
});

There is much more to hapi authentication strategies but this should get you started. It’s time we talk about authorization and how hapi streamlines this process.

For hapi to have user authorization we need to set the user’s scope. Scope is used in many authorization implementations, including JSON Web Token, and it defines the permissions or roles the user has access to. Here are two user objects with scope.

{
id: 123,
email: ‘guest@guest.com’,
scope: ‘user’
},
{
id: 124,
email: ‘admin@admin.com’,
scope: [‘user’, ‘admin’]
}

This was just like the user object earlier but with the scope property. Scope can be one item or an array of items. Now in our login route when we set the user’s session, request.auth.session.set(user), it will contain the user’s scope and we can use authorization. For a route to use authorization just add the allowed scopes to the route auth object.

server.route({
method: ‘GET’,
path: ‘/example’,
config: {
auth: {
strategy: ‘base’,
scope: ‘user’ // or [‘user’,’admin’]
},
handler: function(request, reply) {
return reply(‘Success, you can access a secure route!’);
}
}
});

Just like a user scope the route scope contain one item or array of items. To authorize a user for a route hapi will check to see if an item in the user scope matches an item in the route scope.

Earlier we set the default strategy to be ‘base’ and we can also make the default scope required for a route to be ‘admin’. This way our routes are the most secure by default.

server.auth.default({
strategy: ‘base’,
scope: ‘admin’
});

And now we have authorization and authentication for our hapi server! Surprisingly it was very easy for us to create a versatile and secure application. Powerful features like these are the reason to choose hapi as your node server framework.

If you are interested in seeing everything that was discussed I created a working example on Github.
https://github.com/poeticninja/hapi-authentication-and-authorization