Next.js  

How to add role-based authentication in Next.js using middleware?

Introduction

Role-based authentication is a common requirement in modern web applications. It ensures that users can access only those pages or features meant for their role, such as admin, editor, or normal user. Next.js makes this process much simpler with the help of middleware. Middleware allows you to intercept requests before they reach a protected page and perform checks like verifying authentication and validating user roles. In this article, we will learn how to implement role-based authentication in Next.js using middleware in simple words and step-by-step instructions.

What Is Middleware in Next.js?

Middleware in Next.js runs before a request is processed. Think of it as a checkpoint that decides whether the user should continue to the page or be redirected elsewhere.

Key uses

  • Checking if a user is logged in

  • Verifying user roles (admin, editor, user)

  • Blocking unauthorized access

  • Redirecting users based on permissions

Middleware runs at the edge runtime, making it fast and efficient for authentication.

Why Use Role-Based Authentication?

Role-based authentication (RBAC) helps applications control access based on user roles. It makes your Next.js application more secure and prevents users from viewing restricted pages.

Examples

  • Only admins can access admin dashboard

  • Only editors can edit content

  • Regular users can access normal pages

This system helps ensure proper control and improves security.

Creating a Sample Authentication Flow

To implement role-based authentication, you will need:

  • User login system (JWT or cookies)

  • User roles stored inside the token

  • Middleware to read the role from the token

Common format of a decoded JWT:

{
  "id": "123",
  "name": "John",
  "role": "admin"
}

The role field is the key part for RBAC.

Step 1: Create Middleware File in Next.js

Next.js automatically detects middleware when you create a middleware.js file in the root or inside the src folder.

Example folder structure

src/
  middleware.js
  app/
    admin/
    dashboard/
    page.js

Step 2: Define Protected Routes

You must define which routes require role-based access. For example:

export const config = {
  matcher: ["/admin/:path*", "/dashboard/:path*"]
};

This tells Next.js to run middleware for these routes.

Step 3: Read Token Inside Middleware

Here is a basic example of reading JWT data from cookies inside middleware:

import { NextResponse } from "next/server";
import { jwtVerify } from "jose";

export async function middleware(req) {
  const token = req.cookies.get("authToken")?.value;

  if (!token) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  try {
    const { payload } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET));
    req.user = payload;
  } catch (error) {
    return NextResponse.redirect(new URL("/login", req.url));
  }
}

This middleware checks:

  • If token exists

  • If token is valid

  • Extracts user details

Step 4: Add Role-Based Route Protection

Now add logic to allow only specific roles to visit certain pages.

Example

export async function middleware(req) {
  const token = req.cookies.get("authToken")?.value;
  const url = req.nextUrl.clone();

  if (!token) {
    url.pathname = "/login";
    return NextResponse.redirect(url);
  }

  const { payload } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET));
  const role = payload.role;

  if (req.nextUrl.pathname.startsWith("/admin") && role !== "admin") {
    url.pathname = "/unauthorized";
    return NextResponse.redirect(url);
  }

  if (req.nextUrl.pathname.startsWith("/dashboard") && !["admin", "editor"].includes(role)) {
    url.pathname = "/unauthorized";
    return NextResponse.redirect(url);
  }

  return NextResponse.next();
}

This logic ensures:

  • Only admin can access /admin

  • Admin or editor can access /dashboard

  • Others are redirected to /unauthorized page

Step 5: Create Unauthorized Page

Create a simple page for unauthorized users.

Example

export default function Unauthorized() {
  return (
    <div>
      <h1>Access Denied</h1>
      <p>You do not have permission to view this page.</p>
    </div>
  );
}

This improves user experience and avoids confusion.

Step 6: Storing Roles in the Token

When users log in, add a role field in the JWT.

Example login code

import jwt from "jsonwebtoken";

export function loginUser(user) {
  const token = jwt.sign(
    {
      id: user.id,
      name: user.name,
      role: user.role
    },
    process.env.JWT_SECRET,
    { expiresIn: "1d" }
  );

  return token;
}

This allows middleware to verify permissions.

Step 7: Testing Role-Based Authentication

To test the setup:

  1. Log in with a normal user → try accessing /admin

  2. Log in as admin → access allowed

  3. Log in as editor → dashboard works, admin does not

This confirms RBAC is working correctly.

Best Practices for Secure Role-Based Authentication

  • Always store JWT secrets in environment variables

  • Never expose role permission logic on the frontend

  • Use HTTPS for secure cookie transfer

  • Keep user roles simple and organized

  • Create a fallback page for unauthorized access

  • Use short token expiry for extra security

Summary

Role-based authentication in Next.js using middleware is a clean, fast, and secure way to restrict access based on user roles. By verifying tokens and checking user permissions before a request reaches the page, you can protect sensitive routes like admin dashboards and internal dashboards. With middleware, JWT roles, and structured route permissions, your Next.js application becomes highly secure, scalable, and easier to maintain.