Back

Leveraging Nuxt.js' Middleware for Routing and Authentication

Leveraging Nuxt.js' Middleware for Routing and Authentication

Middleware is a software layer (actually a file in this context) that intercepts and processes requests before they reach their intended destination. They are often used to perform tasks such as authentication, logging, or modifying requests and responses. Middleware is the recommended solution to significantly increase your application security, and this article will show you how to use it in Nuxt.js.

Have you ever boarded an aircraft? If so, you know that before passengers are allowed to board a flight, they typically go through security checkpoints. Security personnel execute various checks at these checkpoints such as baggage screening, metal detector scans, and identity verification. These security measures are implemented to ensure the passengers’ and aircraft’s safety and security.

Similarly, in the context of web development with middleware, certain operations may need to be executed before a user is directed to a particular route or page. These operations could include authentication checks, authorization checks, data validation, or any other pre-processing tasks necessary to secure the application. This is precisely what we aim to achieve in this article.

Authentication and Authorization problems encountered without Middleware

For authentication, without middleware, developers might need to manually implement authentication checks in each route or page, leading to code duplication and increased complexity. Additionally, managing user sessions and securely storing credentials becomes challenging without a centralized authentication mechanism.

Similarly, handling authorization can become cumbersome. Developers might resort to scattered authorization logic throughout the codebase, making maintaining and updating permissions difficult. Without a central control, there could be different access levels in various parts of the application, which might let unauthorized users see sensitive data. That’s something we don’t want.

How to curb the aforementioned problem

Middleware helps solve authentication and authorization issues by providing a central and easy way to handle them. It lets developers set up authentication mechanisms once and then apply them uniformly across all routes or pages. This reduces code duplication and ensures consistent and secure authentication across the application.

Furthermore, it allows developers to implement authorization logic in a central location. Middleware also aids in implementing access control based on user roles, permissions, or other criteria by intercepting incoming requests before they reach the route handlers.

Types of route middleware in Nuxtjs

In a Nuxt.js application, route middleware can be categorized into three types: anonymous, named, and global middleware. Each type serves different purposes and can be implemented in various scenarios, as we will see shortly.

Anonymous Middleware

Anonymous middleware is applied directly to a specific route without being given a name, as seen below.

<script setup>
definePageMeta({
  middleware: defineNuxtRouteMiddleware((to, from) => {
    console.log("anonymous middleware");
    console.log("to", to);
    console.log("from", from);
  }),
});
</script>

This type is useful for implementing logic specific to a particular route and doesn’t need to be reused elsewhere. It’s handy for tasks like route-specific authentication checks or data fetching before rendering the page.

Named Middleware

Named middleware assumes the name of the file where it is declared. It needs to be declared in the “middleware” directory, and as we will see shortly, it can be applied to any page by just importing.

This type is beneficial for applying common logic to multiple routes. It’s suitable for tasks such as authentication for a group of routes or logging actions before rendering specific pages.

// middleware/auth.tsx file
export default defineNuxtRouteMiddleware(async (to, from) => {
  console.log("middleware/auth");
  console.log("to", to);
  console.log("from", from);
});

To apply the Named middleware to any route, do the following in the page file.

// pages/profile.vue file
<script setup>
  definePageMeta({
    middleware: 'auth' // for a single middleware
    // or {/* middleware: ["auth"]  */} // An array for multiple middlewares
  })
</script>

Global Middleware

Global middleware applies to all routes in the application. Like Named middleware, Global middleware needs to be declared in the “middleware” directory, but its file should have a .global suffix.

This type is ideal for implementing universal behaviors or functionalities that should be applied to every route in the application. It’s commonly used for tasks like setting up global error handling, initializing application-wide state, or performing common data fetching operations.

export default defineNuxtRouteMiddleware(async (to, from) => {
  console.log("middleware/auth.global");
  console.log("to", to);
  console.log("from", from);
});

You don’t have to explicitly call the global middleware in any particular file, as it automatically applies to all pages. However, to prevent a global middleware from being applied to a specific route, you can modify the exported function, as shown below.

export default defineNuxtRouteMiddleware(async (to, from) => {
  if (to.fullPath === "/dashboard") return;
  console.log("middleware/auth.global");
  console.log("to", to);
  console.log("from", from);
});

The snippet above shows that the global middleware will not be executed for requests made to the /dashboard route.

A Step-by-Step Guide to Utilizing Middleware in Nuxt.js Applications

Let’s begin by exploring how global middleware can be employed. One significant real-life scenario involves implementing a maintenance mode for the entire website.

To avoid boreing you with some details, I’ve shared a starter file for you to follow. Access it here. If you need guidance on cloning or downloading, refer to this article.

After cloning or downloading the repository, install all the required dependencies and launch the application. You should see the following user interface.

image

Now that we are on the same page let us go back to implementing our maintenance mode feature.

Let’s get started by creating the page’s UI. Create “maintenance.vue” in the pages folders, then copy and paste the code in the snippet below.

<script setup>
definePageMeta({
  layout: false,
});
</script>

<template>
  <div class="maintenance-container">
    <img
      src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQBcBYWudeH_bn6w-LcUzhLPhf9PGhkhy-1Qg&usqp=CAU"
      alt="Maintenance"
      class="maintenance-image"
    />
    <div class="maintenance-content">
      <h1 class="maintenance-title">Website Under Maintenance</h1>
      <p class="maintenance-message">
        We are currently undergoing maintenance. Please check back later.
      </p>
    </div>
  </div>
</template>

<style scoped>
.maintenance-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  width: 100%;
  background-color: #f0f0f0;
}

.maintenance-image {
  max-width: 300px;
  margin-bottom: 20px;
}

.maintenance-title {
  font-size: 28px;
  color: #333;
  margin-bottom: 10px;
  text-align: center;
}

.maintenance-message {
  font-size: 18px;
  color: #666;
  text-align: center;
}
</style>

If you observe in the snippet above, you’ll notice that layout is set to false in the script tag. This ensures that the default layout applied to all pages isn’t applied to the /maintenance page. Want to know more about the layout in Nuxt? check here.

After that, make a new folder called “middleware” and make a file named “maintenance.global.ts” inside it. Then, copy and paste the code from the snippet below into the file.

export default defineNuxtRouteMiddleware(async (to, from) => {
  const isUnderMaintenance = true;

  if (from.path === to.path || to.path === "/maintenance") return;

  if (isUnderMaintenance) {
    return navigateTo("/maintenance");
  }
});

Let me walk you through the code in the snippet above.

  • The isUnderMaintenance variable is set to true. In real-life scenarios, it’s typically configured in server settings, API endpoints, database configurations, or integrated external services. It can be manually or automatically updated to reflect the current maintenance state.

  • The first if statement includes two checks to prevent an infinite loop. This condition ensures that navigation is halted if the destination path is the same as the current path (from.path === to.path) or if the destination path is the maintenance page (to.path === "/maintenance").

  • Lastly, if the application is under maintenance (as indicated by the isUnderMaintenance variable being true), it redirects the user to the “/maintenance” route.

And with that, the implementation of the maintenance mode features is complete. This is one good way of utilizing the global middleware.

You should now have the same functionality demonstrated in the video below.

Authorization and Authentication using Middleware

Now, let’s proceed to implement authentication and authorization using middleware.

Currently, none of the routes in our application are safeguarded, allowing unauthorized access to sensitive data. Let us implement the logic to prevent this.

The login and signup pages have been created in the Pages folder of the copied repository, and the functionalities have been implemented for us. Let me quickly explain what we have on each page.

Signup page

The code on the signup page performs the following:

  • Imports necessary methods from Vue and sets up the router.
  • It references the signup form’s state with empty name and password fields.
  • When the signup function is called, it retrieves existing users from local storage, appends the new user details, and stores them back.
  • Finally, it redirects the user to the login page.

Login page

The code on the login page performs the following:

  • Sets up the login form with fields for name and password.
  • On form submission, it retrieves existing users from local storage and checks if the credentials entered match those of any registered user.
  • If a match is found, it authenticates the user and redirects the user to the profile page.

Do note that Local storage is convenient but is not the most secure option for authentication state. In real-life scenarios, you should consider more secure methods, such as encrypted cookies or tokens.

Don’t forget to comment out the logic in maintenance.global to avoid continuous redirection to the maintenance page.

Moving forward, let’s create a named middleware. Do you recall the process? If you’re unsure, let’s create it together.

Create an “auth.ts” file in the middleware directory and copy and paste the code in the snippet below into it.

// middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
  if (from.path === to.path) return;

  const isAuthenticated: boolean = JSON.parse(
    localStorage.getItem("isAuthenticated") || "false"
  );

  if (!isAuthenticated) {
    return navigateTo("/login");
  }
});

Let me walk you through the logic in the snippet above.

  • As you are already familiar with, the middleware is defined using Nuxt’s defineNuxtRouteMiddleware function.
  • It checks if the current route is the same as the previous route (from.path === to.path). If so, it returns to prevent an infinite loop.
  • It retrieves the authentication state from local storage using localStorage.getItem("isAuthenticated").
  • The retrieved value is parsed as a boolean using JSON.parse(). If no value is found, it defaults to false.
  • If the user is not authenticated (!isAuthenticated), it redirects them to the login page using the navigateTo("/login") function.

Now, the remaining task is to apply this middleware to the specific pages( profile and dashboard) where we want it to be utilized, and we will do that by simply doing the following.

// pages/profile.vue
<script setup>
definePageMeta({
  middleware: "auth",
});
</script>
// pages/dashboard.vue
<script setup>
definePageMeta({
  middleware: "auth",
});
</script>

With this implemented, both the profile and dashboard routes should now be restricted to authenticated users, as demonstrated in the video below.

Best Practices for utilizing Middleware

  • Use meaningful names for middleware files to easily identify their purpose.
  • Keep middleware functions concise and focused on a single responsibility.
  • Document each middleware function’s purpose and behavior to facilitate understanding and usage.

Conclusion

Middleware is an important aspect of development that developers should prioritize, given its versatility and as It enables efficient authentication and authorization checks, among other functionalities as we have seen in the article.

To discover more about Nuxt middleware, click here. Also, remember to practice and experiment on your own. That’s how you improve.

Secure Your Front-End: Detect, Fix, and Fortify

Spot abnormal user behaviors and iron out the bugs early with OpenReplay. Dive into session replays and reinforce your front-end against vulnerabilities that hackers search for.

OpenReplay