hapi — Authentication and Remember Me Using Cookies

Within last week’s tutorial, you’ve learned how protect routes from unauthorized access of users leveraging basic authentication with username and password in hapi.

The downside of this approach is that users need to put in their credentials on every request. Even just a site refresh forces them to log in again … and again on the next page … and you see where we’re going.

This guide walks you through the usage of hapi and the hapi-auth-cookie plugin to implement a remember me functionality with sessions based on cookies.

Before diving into the details, have a look at the series outline and find posts that match your interests and needs.

hapi Series Overview

Authentication in Hapi

We’ve already touched the underlying mechanisms for authentication in hapi within the basic authentication guide. Nonetheless, let’s shortly recapture what’s behind the scenes. We’ll dive deeper into the details of authentication later within this hapi series.

As of now, it’s important to know that hapi uses a scheme and strategy mechanism for authentication. Picture yourself that schemes are general types of authentication like basic or digest. In reference, a strategy is the actual reference and named instance of an authentication scheme. This abstract concept is hard to grasp without an example and we’ll do that within a minute using the hapi-auth-cookie plugin.

Cookie Based Sessions in Hapi Including Remember Me

You already guessed that you don’t need to implement the cookie based session handling on your own. Make use of the existing and well tested hapi-auth-cookie plugin that decorates your hapi server instance with a cookieAuth object and brings all required functionality to manage sessions for you.

At first, add hapi-auth-cookie as a dependency to your project and install it. You can combine both steps by executing the following snippet within your command line:

npm i -S hapi-auth-cookie  

Having the plugin installed, you need to register it to your hapi server. Afterwards create a new authentication strategy and make it available to the rest of your app.

const Hapi = require('hapi')

// create new server instance
const server = new Hapi.Server()

async function liftOff() {  
  server.register({
    plugin: require('hapi-auth-cookie')
  })

  server.auth.strategy('session', 'cookie', options) // your TODO: options -> there are required ones

  try {
    await server.start()
    console.log('info', 'Server running at: ' + server.info.uri)
  } catch (err) {
    console.error(err)
    process.exit(1)
  }
}

liftOff()  

If you aren’t familiar with hapi’s plugin system and feel uncomfortable seeing the code above, follow our tutorial on extending your hapi server’s functionalities with plugins within this hapi series and get a fundamental understanding.

Alright, reviewing the code above you see the registration of hapi-auth-cookie plugin to your server instance by using server.register(require('hapi-auth-cookie')). Internally, the plugin adds a new authentication scheme to your hapi server called cookie. From now on, you can create a new strategy that uses the newly created cookie scheme. To create an authentication strategy for your hapi server, leverage the server.auth.strategy(name, scheme, [mode], options) function that expects three ([optionally four]) parameters:

  1. name: your strategy name that will be used throughout your app
  2. scheme: the scheme name on which the strategy is based upon (e.g. cookie)
  3. options: required and optional options

Within the code snippet above, you can see that the cookie scheme is used to create a new strategy called session. You could use the same name for strategy and scheme. For this tutorial, it’s a lot easier to keep context when choosing different names so you don’t get confused what’s actually in reference.

The fourth, optional mode parameter in server.auth.strategy(name, scheme, [mode], options) is used to indicate whether the new scheme should be the default one and/or be applied to every route that is defined. We’ll have a look at this in the upcoming tutorial. Stay tuned :)

hapi-auth-cookie — Options

There are multiple options to customize the behavior of hapi-auth-cookie to your needs. Please find the complete list in the linked GitHub repository. It’s redundant to point out each of the 14 available options, which will just bloat this guide.

Actually, there’s a single required option when creating an authentication strategy based on the cookie scheme: a cookie password to encode it.

  • password: used to encode the cookie by Iron. Requires a length of at least 32 characters.

The other options are optional and follow the principle of convention over configuration by falling back to a default value. However, you can adjust the individual options as you need.

Validate User Credentials

If you’ve read the previous tutorial on basic authentication with hapi, you may wonder if it’s also required to provide a validation function that check the user’s credentials. Actually, the validation function is a valid option that can be used to authenticate and verify the provided data, and also to perform other checks like if the user is still registered on your platform. It’s not a required function for hapi-auth-coookie to do its work properly.

Route Configuration to Require Authentication

By default, hapi doesn’t require any kind of authentication for specified routes. You have to restrict access with a default or required authentication strategy or individually on a route basis and you’re free to choose from multiple authentication strategies if you’re using more than just cookie based sessions. You’ll learn how to specify multiple authentication strategies for a route within a later tutorial.

Defining authentication for a route requires you to provide a config object for the route. In a simple setup, you would set the method, path and handler for a route. Now the handler moves into the route’s config object. To ultimately set the authentication mechanism(s) for a route, set your desired strategy name as a string value to the auth field. The following snippet visualizes the description:

server.route({  
  method: 'GET',
  path: '/private-route',
  config: {
    auth: 'session',
    handler: (request, h) => {
      return 'Yeah! This message is only available for authenticated users!'
    }
  }
})

The route’s config object has the auth field set to the previously registered authentication strategy name session. The handler function within the config object is located aside the auth field.

Set a Cookie

You made it through the basics and it’s finally time to set the user’s cookie and remember him for future visits. You’ve to implement a login route that handles the functionality on checking whether the username is actually registered and if yes, do the passwords match. In case everything went smooth and the user authenticated successfully, you can set the individual cookie using the request.cookieAuth.set(object) method. The cookieAuth object is available, because the hapit-auth-cookie plugin decorates the request object.

server.route({  
  method: 'POST',
  path: '/login',
  config: {
    handler: (request, h) => {
      const username = request.payload.username
      const password = request.payload.password

      // check if user exists in DB
      // compare passwords

      // if everything went smooth, set the cookie with "user" specific data
      request.cookieAuth.set(user);

      return 'Wohoo, great to see you'
    }
  }
})

The .set(object) method sets the current session data after successful login. The object can’t be null and is available in subsequent requests as request.auth.credentials.

Once you have a valid session object stored, you can also use the .set(key, value) to set or update the given key with provided value. You can’t use the .set(key, value) method without having set a proper object first.

Check If Cookie Is Set and User Is Authenticated

Hapi’s request.auth object provides the helper field request.auth.isAuthenticated that indicates either the user is already authenticated and you can access its session data via request.auth.credentials or not.

server.route({  
  method: 'GET',
  path: '/some-route',
  config: {
    auth: {
      mode: 'try',
      strategy: 'session'
    },
    handler: (request, h) => {
      if (request.auth.isAuthenticated) {
        // session data available
        const session = request.auth.credentials

        return 'Bro, you’re already authenticated :)'
      }

      // further processing if not authenticated …
    }
  }
})

Obviously, the user’s session data isn’t available if the user isn’t authenticated correctly. Be careful with your app and check if you receive proper values when requesting the stored session data.

Clear the Cookie — Logout

Of course, there are situations where you want to clear previously saved session data, like log the user out. In those cases, make use of the request.cookieAuth.clear([key]) method. The key parameter is optional and if not provided, the complete session will be deleted.

server.route({  
  method: 'GET',
  path: '/private-route',
  config: {
    auth: 'session',
    handler: (request, h) => {
      // clear the session data
      request.cookieAuth.clear()

      return 'Logged out. See you around :)'
    }
  }
})

If you just want to clear a field within your session object, provide the key parameter for the .clear(key) method. So for example, if you want to remove the user’s email from the session data, use request.cookieAuth.clear(email) to remove it.

Full Context — Complete Code Snippet

Throughout this guide we’ve used only short code snippets to clarify the points of interest. Typically, the user has to be authenticated via web forms which means that views are required and also the functionality to validate and authenticate the provided user credentials. We’ve prepared a sample project that presents a minimal required setup to implement a cookie-based session management. The code is publicly accessible on GitHub. If you’re interested in the details, please head over to GitHub and check out the linked repository.

Outlook

This guide gives you a brief overview on how to implement cookie-based session handling that will allow you to recognize existing users and personalize your application. Keep the existing hapi-auth-cookie plugin in mind and leverage its functionality to set a personalized cookie that automatically authenticates a user for subsequent requests. If the user logs out of your app, just clear the previously set cookie and erase all traces.

Additionally to the cookie-based authentication, you can benefit from OAuth and use third-party services like GitHub to authenticate users for your platform.

We appreciate feedback and love to help you if there’s a question in your mind. Let us know in the comments or on twitter @futurestud_io.

Make it rock & enjoy coding!


Additional Resources

Explore the Library

Find interesting tutorials and solutions for your problems.