Introduction

Authentication is a fundamental feature of any secured web application. It is the process of identifying a user’s identity and serve the particular content authorized to the user.

In this article, I would like to introduce Cookie-based Authentication. We will learn about Cookie Authentication and implementation using Hapi.js, which is a lightweight Node.js framework.

What are Cookies?

In the context of computing, a cookie or formally referred to as HTTP cookie, contains some particular information about the user acquired via their visit to a particular website. !! It does not contain information like Credentials but information like preferences based on his activity or details about the user.

An example for preferences based on activity is dark mode. Some websites may keep a cookie to set the user’s preference to dark/light upon his first visit. On the next visit, this is remembered and the user’s prefered theme is set. An example for user details can be geographical info, browser info, session ids for websites, add preferences etc.

Yup!. I’ve implemented this in my blog too. Have you noticed the dark theme btw ? Access the sidebar menu from top left and explore!

Commonly cookies are used to track website activity.

Cookie authentication provides simple cookie-based session management. It can be used to store session information of a particular user. Cookies are often used to keep state about a user between requests. Here, when the user is authenticated by web form, oauth etc. the browser receives a reply with a session cookie, this information is encrypted and stored in the browser.

Now, this can be used to keep a user logged in for a time period (cookie ttl) or terminate a session after browser closes etc.

In Hapi.js, cookies are encrypted via IRON algorithm.

I’m breaking the concepts down into several pieces, refer the last section for the entire source code.

Let’s first create a Hapi.server,

'use strict';

const Hapi = require('@hapi/hapi');
const cookie = require('@hapi/cookie');

const init = async () => {
  const server = Hapi.server({
    host: 'localhost',
    port: 8888,
  });
  await server.start();
  console.log(`Server running at 8888`);
  module.exports.server = server;
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

Now, we need to register the cookie plugin and set a strategy. The first parameter of auth.stategy is the name of the cookie, here it’s session. The next parameter indicates the type of auth, here it’s cookie. The last parameter is a cookie object.

The cookie scheme takes the following options:

  • name - the cookie name. Defaults to sid.
  • password - used for Iron cookie encoding. Should be at least 32 characters long.
  • ttl - sets the cookie expires time in milliseconds. Defaults to single browser session (ends when browser closes). Required when keepAlive is true.
  • path - sets the cookie path value. Defaults to none.
  • isSameSite - if false omitted. Other options Strict or Lax. Defaults to Strict.
  • isSecure - if false, the cookie is allowed to be transmitted over insecure connections which exposes it to attacks. Defaults to true.
  • redirectTo - optional login URI or function function(request) that returns a URI to redirect unauthenticated requests to.
  • keepAlive - if true, automatically sets the session cookie after validation to extend the current session for a new ttl duration. Defaults to false.

Refer: https://hapi.dev/module/cookie/api/

There are more options available, refer to the official documentation.

Now, we set a cookie strategy and a default strategy. I’m setting a default strategy here, so that we don’t need to mention this on every route. In the case of a production build or a more complex web app which includes OAUTH. Additional plugins like Bell, JWT etc. is used. Generally a combination of this and cookie auth. In this case, for every route that needs a particular stategy can be specified in the options.

await server.register(cookie);

server.auth.strategy('session', 'cookie', {
  cookie: {
    name: 'test-cookie',
    password: 'super-secure-cookie-pass-at-least-32chars',
    isSecure: false, //In Prod should be True.
    ttl: 60 * 60 * 1000,
    isSameSite: 'Lax',
    path: '/',
  },
  redirectTo: false,
  keepAlive: true,
});
server.auth.default('session');

Now, lets create some routes.

/test/login
/test/res
/test/api
/test/logout

The /test/login route will call the set method of cookie and a cookie will be set. The /test/res is a restrcited route, which can be only accessed if authenticated. Once a cookie is set, this route will be accessible. The /test/api is also a test route

server.route([
  {
    method: 'GET',
    path: '/test/login',
    options: {
      auth: { mode: 'try' },
      handler: (request, h) => {
        const id = Math.random();
        request.cookieAuth.set({ uid: id });
        console.log(`coookie is set for id : ${id}`);
        return '<h1>Login success!</h1>';
      },
    },
  },
  {
    method: 'GET',
    path: '/test/res',
    options: {
      auth: { mode: 'required' },
      handler: () => {
        console.log(`authenticated route`);
        return '<h1>In Auth Route</h1>';
      },
    },
  },
  {
    method: 'GET',
    path: '/test/api',
    options: {
      auth: { mode: 'required' },
      handler: (request, h) => {
        return request.auth.isAuthenticated
          ? { message: 'Cookie Test Success' }
          : { message: 'Cookie Test Failed' };
      },
    },
  },
  {
    method: 'GET',
    path: '/test/logout',
    options: {
      auth: { mode: 'try' },
      handler: (request, h) => {
        request.cookieAuth.clear(); // Clearing Cookie
        console.log(`log out.. cookie removed`);
        return '<h1>log out.. cookie removed</h1>';
      },
    },
  },
]);

Pic Credits: Photo by SJ.