How to Implement Role-Based Access Control in Next.js?

Introduction

Role-Based Access Control (RBAC) allows you to manage user permissions and restrict access to different parts of your application based on user roles. Implementing RBAC in a Next.js application involves managing roles, protecting routes, and controlling access. This guide walks you through setting up RBAC in a Next.js application.

Overview

RBAC in Next.js typically involves.

  1. Defining Roles: Setting up user roles in your application.
  2. Assigning Roles: Assigning roles to users during registration or profile updates.
  3. Protecting Routes: Restricting access to pages based on roles.

Setting Up Roles
 

Define Roles in Your Database

Store roles in your database schema. For example, with a MongoDB schema.

// models/User.js
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
  email: String,
  password: String,
  role: {
    type: String,
    enum: ['user', 'admin', 'moderator'],
    default: 'user'
  }
});
export default mongoose.models.User || mongoose.model('User', userSchema);

Assign Roles During Registration

When registering a user, assign a role based on your requirements.

// pages/api/register.js
import User from '../../models/User';
import dbConnect from '../../lib/dbConnect';
export default async function handler(req, res) {
  if (req.method === 'POST') {
    await dbConnect();
    const { email, password, role } = req.body;
    // Create a new user with a specified role
    const user = new User({ email, password, role });
    await user.save();
    res.status(201).json({ message: 'User created' });
  } else {
    res.status(405).json({ message: 'Method not allowed' });
  }
}

Protecting Routes
 

Create a Higher-Order Component (HOC)

A Higher-Order Component can be used to wrap pages and enforce role-based access control.

// components/withRole.js
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
const withRole = (WrappedComponent, allowedRoles) => {
  return (props) => {
    const [userRole, setUserRole] = useState(null);
    const router = useRouter();
    useEffect(() => {
      const fetchUserRole = async () => {
        const res = await fetch('/api/user');
        const data = await res.json();
        setUserRole(data.role);
        if (!allowedRoles.includes(data.role)) {
          router.push('/403'); // Redirect to a forbidden page
        }
      };
      fetchUserRole();
    }, [allowedRoles, router]);
    if (!userRole) return <div>Loading...</div>;
    return <WrappedComponent {...props} />;
  };
};
export default withRole;

Protect Pages

Wrap your pages with the HOC to enforce role-based access.

// pages/admin.js
import withRole from '../components/withRole';
const AdminPage = () => (
  <div>
    <h1>Admin Dashboard</h1>
    {/* Admin content */}
  </div>
);
export default withRole(AdminPage, ['admin']);

Handling Unauthorized Access
 

Create a 403 Forbidden Page

Design a custom page for unauthorized access.

// pages/403.js
const Forbidden = () => (
  <div>
    <h1>403 - Forbidden</h1>
    <p>You do not have permission to access this page.</p>
  </div>
);
export default Forbidden;

Best Practices

  • Secure Roles: Ensure roles and permissions are securely managed and validated on the server side.
  • Clear Messaging: Provide clear feedback to users who attempt to access restricted areas.
  • Regular Audits: Periodically review and update role definitions and permissions.

Summary

Implementing Role-Based Access Control in Next.js involves defining roles, assigning them to users, and protecting routes based on these roles. By using Higher-Order Components and server-side checks, you can effectively manage access and ensure that users can only access the parts of the application for which they are authorized. This approach enhances security and ensures a better user experience by controlling access based on user roles.