OAuth Authentication and Session Management in Hapi.js 8.0 using bell and hapi-auth-cookie

OAuth Authentication and Session Management in Hapi.js 8.0 using bell and hapi-auth-cookie

We recently started on a new project and we’ve decided to use Node.js. After a week or so of researching which framework to use, we came to the conclusion that Hapi.js far exceeded our requirements. In only a week we had a working prototype in place and the quality and structure of the code is something we could be proud of. However, there were some things that didn’t have definitive documentation and we had to figure out a best practice ourselves. One of those things was connecting bell with hapi-auth-cookie. As it turns out, it was super simple, but not completely apparent.

Hapi.js makes it extremely easy to hook up to many of the major OAuth providers (Google, Facebook, Twitter, Github, etc.) using one of the many plugins in the Hapi.js family called bell. This plugin only does one thing and that is authenticate with the provider. It doesn’t create a long lived session for the users of your application, which is where the hapi-auth-cookie plugin comes into play. Once bell is done authenticating with the provider, you simply pass the profile to hapi-auth-cookie and it will store that information in a HttpOnly encrypted cookie for the life of the users session.

Note: This article assumes that you already have an application and are wanting to add in OAuth functionality.

The first step you need to do is install the plugins:

$ npm install --save bell hapi-auth-cookie

Next you need register both bell and hapi-auth-cookie as authentication strategies.

server.auth.strategy('session', 'cookie', {
    cookie: 'sid',
    password: 'cookie_encryption_password',
    redirectTo: '/login'
});

server.auth.strategy('oauth', 'bell', {
    provider: 'github',
    password: 'cookie_encryption_password',
    clientId: '<Github client ID>',
    clientSecret: '<Github Client Secret>'
});

Note: It is best practice and highly recommended that you store your secrets in a server environment variable rather than checking them into source control.

Now all we need to do is create some routes. One to login, another to logout, and any others that need a user logged in to use.

// This route does not require the user to be logged in.
server.route({
    method: 'GET',
    path: '/',
    config: {
        handler: function homePageHandler(request, reply) {
            return reply('Home Page');
        }
    }
});

// This route requires the user to be logged in.  If a user lands
// on this route, it will redirect them to the /login page.
server.route({
    method: 'GET',
    path: '/my-account',
    config: {
        // Use the 'session' auth strategy to only allow users
        // with a session to use this route.
        auth: 'session',
        handler: function myAccountHandler(request, reply) {
            return reply('My Account');
        }
    }
});

// This route is used by bell for the oauth flow to authenticate
// a user with the nominated provider.
server.route({
    method: ['GET', 'POST'],
    path: '/login',
    config: {
        // Use the 'oauth' auth strategy to allow bell to handle the oauth flow.
        auth: 'oauth',
        handler: function loginHandler(request, reply) {
            // Here we take the profile that was kindly pulled in
            // by bell and set it to our cookie session.
            // This will set the cookie during the redirect and 
            // log them into your application.
            request.auth.session.set(request.auth.credentials.profile);

            // User is now logged in, redirect them to their account area
            return reply.redirect('/my-account');
        }
    }
});

// This route is used to the logout the user.  This will **not**
// logout the user from the provider they used to login.
server.route({
    method: 'GET',
    path: '/logout',
    config: {
        handler: function logoutHandler(request, reply) {
            // Clear the cookie
            request.auth.session.clear();

            return reply.redirect('/');
        }
    }
});

And that is all the code you need! Bell uses the handler for the /login route as a callback which you can use to pull out the users profile and assign it to the cookie using request.auth.session.set(). Just by doing that, it will create the users session and they will stay logged in until it’s cleared.

To learn more about how to use different authentication strategies and schemes, you can checkout Hapi’s own tutorial on Authentication.