hapi — Redirect to Previous Page After Login

As with every web form where users need to provide their credentials, a login form can be complex. You know all the situations that can occur for invalid user inputs, like validation errors and the related handling, making sure the user understands what’s wrong, rendering the related input fields in erroneous colors, and so on.

Additionally to all possible errors, you want to provide comfort features like autofill the previously typed username or redirecting the user to the page that requires login.

This tutorial shows you how to implement the redirect to a previously visited page after a session based login with hapi-auth-cookie.

hapi Series Overview

Preparation & Requirements

This tutorial assumes that you’re implementing a cookie-based authentication with hapi-auth-cookie. For other authentication strategies like JWT or OAuth, use this tutorial as an inspiration for your custom implementation :)

Starting Point

Imagine the following scenario: you’re browsing a website and at some point hitting a wall by seeing the login form. To view the visited page you need to authenticate. For a good user experience, developers should implement a redirect after the login that proceeds to the page that was selected by the user.

In that case, the user was interrupted due to the login form, but can move on browsing the private sections immediately and doesn’t need to find its way back to the originally requested site.

The following —simplified— screen illustrates two links for a user: a private page or the user’s profile. Both sites require authentication.

Result redirect to previous page

Let’s say you’ve clicked the private section and due to the authentication requirements you’re redirected to the login page.

As you can see within the url bar, there’s a query parameter called next that keeps the selected link’s relative URL path:

Result redirect to previous page

The question is: how does the app show the login screen and still keep the selected link for a later redirect? Well, read the next section to get the insights!

Configure hapi-auth-cookie to Remember the Selected URL

Authentication in hapi is based on strategies. This tutorial uses cookie-based sessions to authenticate users. With hapi-auth-cookie you can customize various configurations.

To remember a selected URL while showing a login screen, you need to specify two options: redirectTo and appendNext.

In case there’s no existing cookie or the cookie data can’t be validated correctly, the user will be redirected to a specified page, defined by redirectTo. This option requires a relative URL path as its value, like /login.

Enable the appendNext property (boolean) by setting it to true. This option requires redirectTo to be set as well.

The following code snippet illustrates the configuration for your authentication strategy that’s based on hapi-auth-cookie:

/**
 * Register session based auth strategy to store
 * and validate the session cookie received with
 * the user’s requests
 */
server.auth.strategy('session', 'cookie', {  
  password: 'ThisIsASecretPasswordForTheAuthCookie',
  redirectTo: '/login',
  // appends the current URL to the query param "next"
  // Set to a string to use a different query param name

  appendNext: true,  // <-- the important line :)

  isSecure: process.env.NODE_ENV === 'production',
  validateFunc: async (request, session) => {
    // validate the existing session
    const username = session.username

    // find the user with given username (or other data)
    // in your database
    const user = await User.findOne(…)

    if (!user) {
      return { isValid: false }
    }

    return { isValid: true, credentials: user }
  }
})

This strategy configuration allows you to specify session for authentication on individual routes. Visiting the routes that require the session strategy will use the configuration from above.

Routes That Require Authentication

Set the session strategy for routes that require authentication and hapi will handle the validation process. For invalid session cookies, the user will be redirected to the login page.

const routeConfig = {  
  method: 'GET',
  path: '/private',
  options: {
    auth: 'session',
    handler: (request, h) => {
      return h.view('private')
    }
  }
}

If you’re running into the „unknown authentication strategy“ error, follow this guide that shows you how to fix it.

Alright, at this point you have the authentication strategy properly configured and a route that requires authentication. Now it’s time to implement the actual login and use the URL within the next query parameter to redirect the user.

Implement the Login Handler That Redirects to the Previous Page

The following code snippet illustrates the route config that is called for incoming POST requests to do the actual user login. The auth object specifies the try mode for session to check for an existing session cookie, but don’t require a valid one. Within plugins you need to override the default redirectTo configuration to avoid an infinite redirect loop to /login.

Within the handler, check if the user is already authenticated and if yes, immediately redirect to the URL that’s available within the next query parameter.

In case the user isn’t already authenticated, do the actual database lookup for the input data.

Notice that you should apply payload validation for incoming user data. This tutorial skips the payload validation to reduce the amount of code and aim the main focus of a redirect.

const loginRouteConfig = {  
  auth: {
    mode: 'try',
    strategy: 'session'
  },
  plugins: {
    'hapi-auth-cookie': {
      redirectTo: false
    }
  },
  handler: async (request, h) => {
    if (request.auth.isAuthenticated) {
      return h.redirect(request.query.next)
    }

    const username = request.payload.username
    const user = // find user in DB

    if (!user) {
      // no user found with given username
      return h.view('login', {
        username,
        errormessage: 'No user registered with given username'
      }).code(404)
    }

    const password = request.payload.password

    const isValid = await Bcrypt.compare(password, user.password)

    if (isValid) {
      request.cookieAuth.set(user)

      // redirect the user to the previous page
      return h.redirect(request.query.next)
    }

    // given password doesn’t match the stored one
    return h.view('login', {
      username,
      errormessage: 'Your password is wrong'
    }).code(401)
  }
}

The route handler simplifies the database lookup for a given user. You’d need to complete this part with your personal implementation. To compare the passwords, we recommend bcrypt.

Finally, if all checks and comparisons pass successfully, use hapi’s reply.redirect(<url>) method to send the user to the actually requested page. The relative path to that URL is stored within the next query parameter and therefore available using request.query.next.

Notice: a user may manually remove the next query parameter from the browser’s URL and visit /login directly. That will result in a situation where next = undefined. Handle that situation with a default value for next to not run into a reply.redirect(undefined).

To check whether the next query parameter contains an actual value other than undefined, you can extend your could with an additional check:

// check whether there’s a value for "next" query param
// if not, define the default "/"
const next = request.query.next ? request.query.next : '/'

// redirect the user to the previous page
return reply.redirect(next)  

How to Keep the Previous Page URL for Login Errors

Validation errors, forgotten email addresses, usernames, or passwords will raise errors that you need to handle and present properly to the user. When users need more than a single attempt to log in, you still want to remember the previous URL.

Actually, it’s kind of straight forward. The approach we’re using is to avoid the action attribute for the <form> tag completely. This way, the current URL from the browser’s URL bar will be taken as the request’s destination when submitting the form.

<form method="POST">  
    <fieldset>
        <label for="username">Username</label>
        <input type="text" name="username" title="Username" id="username" value="{{username}}">

        <label for="password">Password</label>
        <input type="password" name="password" title="Password" id="password">

        <input class="button-primary" type="submit" value="Log in">
    </fieldset>
</form>  

Even though there may occur errors during the login process, the hapi server will respond the login view again and you’ll return to the exact same URL as before.

The Result

Depending which link the user selected on the startpage or manually added for the next parameter within the URL, your route handler will redirect it to that page after successful login.

Result redirect to previous page

In future requests, the cookie-based session will be validated with the used strategy’s validateFunc for session. Make sure to provide a proper user lookup to authenticate each request.

Outlook

This tutorial shows you how to implement the functionality to redirect users after login to the previously requested page. Those features are convenient to bring users back into context when hitting them with a login wall for pages that require authentication.

When using the hapi-auth-cookie plugin, you’ve already the largest part in place. Update your existing login handler with little effort and users will love it :)

We’re happy to hear your thoughts and comments. Please don’t hesitate to use the comments below or find us on Twitter @futurestud_io. Let us know what you think!

Enjoy coding & make it rock!

Explore the Library

Find interesting tutorials and solutions for your problems.