NextJS - Security . 11 Mar, 2024 . By Mesut KILINCASLAN

Role-Based Access Control in Next.js with NextAuth

shape
Role-Based Access Control in Next.js with NextAuth

In the rapidly evolving world of web development, ensuring that your application has robust and secure access control is paramount. One of the challenges we often face is implementing a flexible yet straightforward role-based access control (RBAC) system that scales with our application's needs. In this blog post, we'll dive into how you can leverage Next.js and NextAuth to create a dynamic RBAC system that can handle complex access patterns, including deeply nested and dynamic routes.

Define the Access Control List (ACL)

At the core of our RBAC system is the access control listACL. This object defines which roles have access to specific endpoints within our application. For example, a "admin" can access a wide range of endpoints, from the homepage to detailed project views and payment information. In contrast, a "visitor" has more limited access, focusing mainly on informational endpoints.

The beauty of this setup lies in its simplicity and flexibility. By defining roles and their associated endpoints in a central object, we can easily update access rights as our application grows and evolves.

        
const roleAccessMap = {
  "admin": [
    "/",
    "/companies",
    "/companies/[id]",
    "/tickets",
    "/tickets/[id]",
    "/team",
    "/payments",
    "/payments/[id]",
    "/analytics",
  ],
  "cs": [
    "/",
    "/companies",
    "/companies/[id]",
    "/tickets",
    "/tickets/[id]",
    "/team",
  ],
  "developer": [
    "/",
    "/projects",
    "/projects/[id]",
    "/account",
    "/company",
    "/tickets",
    "/tickets/[id]",
    "/team",
  ],
  "visitor": [
    "/",
    "/projects",
    "/account",
    "/company",
    "/support",
  ],
};
        
    

Deep Link Access Control Logic

/companies/[id] or /tickets/[id], requires a more nuanced approach. This is where our doesRoleHaveAccessToURL function shines. By converting our defined routes into regex patterns, we can accurately determine whether a role has access to a requested URL, even if that URL includes dynamic segments.

This approach ensures that our access control logic remains concise and maintainable, regardless of how complex our application's routing becomes.

        
function doesRoleHaveAccessToURL(role, url) {
    const accessibleRoutes = roleAccessMap[role] || [];
    return accessibleRoutes.some(route => {
        // Create a regex from the route by replacing dynamic segments
        const regexPattern = route.replace(/[.*?]/g, "[^/]+").replace("/", "\/");
        const regex = new RegExp('^regexPattern$');
        return regex.test(url);
    });
}
        
    

Middleware for Authentication and Role Control

Integrating this RBAC system with NextAuth is straightforward thanks to Next.js middleware. Our custom middleware function checks the user's role against the requested URL's access rights. If the user doesn't have the appropriate access, they're redirected to a 403 error, enhancing the application's security posture.

        
export default withAuth(
  function middleware(req) {
    // Redirect to login page if there is no accessible token
    if (!req.nextauth.token) {
      return NextResponse.redirect("/auth/login");
    }

    const role = req.nextauth.token.role;
    let haveAccess = doesRoleHaveAccessToURL(role,req.nextUrl.pathname)
    if(!haveAccess)
    {
      // Redirect to login page if user has no access to that particular page
      return NextResponse.rewrite(new URL("/403", req.url));
    }
    
    // Allow
  },
);
        
    

Fine-Grained In-Page Access Levels

Beyond controlling route access, our system provides a mechanism for managing in-page action permissions through a custom hook, useRoleAccessLevel. This hook takes a page name and optionally a user role, then returns the permissions for editing or removing content on that page. This allows for a nuanced control of UI elements based on the user's capabilities, enhancing the application's security and user experience.

First we need to define the role access level object for each page that we wanna set accesses.

        
const roles = {
  'admin': {
    projects: { edit: 1, remove: 0 },
    ticket: { edit: 1, remove: 1 },
    company: { edit: 1, remove: 0 },
  },
  'developer': {
    projects: { edit: 1, remove: 0 },
    ticket: { edit: 1, remove: 1 },
    company: { edit: 1, remove: 0 },
  },
  'visitor': {
    projects: { edit: 1, remove: 0 },
    ticket: { edit: 1, remove: 0 },
    company: { edit: 0, remove: 0 },
  },
};
        
    

Then let's define the hook to recognize the active user role and active page;

        
// It's optional to define page and role here
const useRoleAccess = ({page, role}: {page?: string; role?: string}) => {
  const session = useSession();
  const router = useRouter();

  if (session.status === 'loading' || session.status === "unauthenticated") return {};

  if (!role) {
    role = session.data && session.data.user.role;
  }

  if (!page) page = router.pathname;

  const accessLevels = roles[role]?.[page];

  return { editLevel: accessLevels.edit, removeLevel: accessLevels.remove};
};

export default useRoleAccess;
        
    

Using this hook, developers can conditionally render elements or enable actions, effectively tailoring the UI to match the user's permissions in the page.


    const { editLevel, removeLevel } = useRoleAccess();

    

Conclusion

Implementing a sophisticated RBAC system in NextJS with NextAuth not only enhances the security of your application but also provides a seamless and dynamic user experience. By carefully mapping roles to routes, employing regex for dynamic route matching, and leveraging hooks for in-page permissions, developers can create highly customizable and secure applications.

Welcome to Commt, I am here to guide you!
Chat with Commt AI Bot
commt-chatbot-ai-avatar

Hi, welcome!

I am Commt's AI user, which is a future feature that developers work on, to provide for the clients to re-train and integrate into Commt SDKs.

How can I help you? 😇

02:13 PM