DEV Community

Cover image for Role & Permission Based Access Control in React (Static Access)
Naser Rasouli
Naser Rasouli

Posted on

Role & Permission Based Access Control in React (Static Access)

When building an admin panel, one of the most important concerns is how to handle access control. Not every user should be able to see or do everything. For example, admins may be able to create and delete users, while editors can only view and update, and viewers should only see information without making changes.
There are many ways to implement access control, but in this article we’ll focus on a simple and static approach. This means roles and permissions are defined in the codebase (not coming from an API), which is often enough for small to medium projects or when your access rules are not supposed to change frequently.

We’ll break it down into two parts:

  1. Route-level access: making sure users can only navigate to the pages they are allowed to.
  2. Component-level access: showing or hiding specific buttons, menus, or features inside a page depending on permissions. This approach keeps your project clean, scalable, and ready to extend if later you decide to fetch permissions dynamically from your backend.

1. Define Roles and Permissions

The first step is to define the roles and their associated permissions. Since we are working with static access control, we can hardcode them inside a roles.ts file.

// roles.ts
export const ROLES = {
  ADMIN: "ADMIN",
  EDITOR: "EDITOR",
  VIEWER: "VIEWER",
} as const;

export const PERMISSIONS = {
  USER_CREATE: "USER_CREATE",
  USER_DELETE: "USER_DELETE",
  USER_VIEW: "USER_VIEW",
} as const;

export const roleAccess = {
  [ROLES.ADMIN]: ["dashboard", "users", "settings"],
  [ROLES.EDITOR]: ["dashboard", "users"],

};

export const rolePermissions = {
  [ROLES.ADMIN]: [PERMISSIONS.USER_CREATE, PERMISSIONS.USER_DELETE, PERMISSIONS.USER_VIEW],


};

Enter fullscreen mode Exit fullscreen mode

Here we defined:

  • ROLES: user types (admin, editor, viewer).
  • PERMISSIONS: actions that can be allowed (create, delete, view).
  • roleAccess: which pages each role can access.
  • rolePermissions: which actions each role can perform.

2. Route-Level Access with ProtectedRoute

We need to prevent unauthorized users from accessing certain routes. For example, a viewer should not be able to open the users management page.
We can build a ProtectedRoute component that checks if the current user’s role is included in the allowed list. If not, it redirects them to an “unauthorized” page.

// ProtectedRoute.tsx
import { Navigate } from "react-router-dom";
import { useAuth } from "@/hooks/useAuth";
import { roleAccess, ROLES } from "./roles";

export const ProtectedRoute = ({
  children,
  allowed,
}: {
  children: JSX.Element;
  allowed: string[];
}) => {
  const { role } = useAuth();

  if (!role || !allowed.includes(role)) {
    return <Navigate to="/unauthorized" replace />;
  }

  return children;
};

Enter fullscreen mode Exit fullscreen mode

And we can use it in our routes configuration like this:

// routes.tsx
import { ProtectedRoute } from "./ProtectedRoute";
import { roleAccess, ROLES } from "./roles";
import DashboardPage from "@/features/dashboard/pages/DashboardPage";
import UsersPage from "@/features/users/pages/UsersPage";

export const routes = [
  {
    path: "/dashboard",
    element: (
      <ProtectedRoute allowed={roleAccess[ROLES.VIEWER]}>
        <DashboardPage />
      </ProtectedRoute>
    ),
  },
  {
    path: "/users",
    element: (
      <ProtectedRoute allowed={roleAccess[ROLES.EDITOR]}>
        <UsersPage />
      </ProtectedRoute>
    ),
  },
];

Enter fullscreen mode Exit fullscreen mode

Now, only the roles listed in roleAccess will be able to visit these routes.

3. Component-Level Access with AccessControl

Route-level control is not always enough. Often you’ll want to hide or show specific UI elements inside a page depending on the user’s permissions. For example, only admins should see a “Delete User” button.
We can create an AccessControl component that checks for a specific permission.

// AccessControl.tsx
import { ReactNode } from "react";
import { useAuth } from "@/hooks/useAuth";
import { rolePermissions } from "./roles";

interface Props {
  permission: string;
  children: ReactNode;
}

export const AccessControl = ({ permission, children }: Props) => {
  const { role } = useAuth();

  if (!role) return null;

  const permissions = rolePermissions[role] || [];

  return permissions.includes(permission) ? <>{children}</> : null;
};

Enter fullscreen mode Exit fullscreen mode

Usage example inside a page:

import { AccessControl } from "@/components/AccessControl";
import { PERMISSIONS } from "@/routes/roles";

function UsersPage() {
  return (
    <div>
      <h1>User List</h1>

      <AccessControl permission={PERMISSIONS.USER_CREATE}>
        <button>Add User</button>
      </AccessControl>

      <AccessControl permission={PERMISSIONS.USER_DELETE}>
        <button>Delete User</button>
      </AccessControl>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Conclusion
With just a few simple steps, we created a clean static access control system in React:

  • ProtectedRoute handles route-level access.
  • AccessControl handles UI-level access.
  • Roles and permissions are defined in one place, making it easy to manage.

This approach is perfect for projects where roles and permissions are not changing often. Later, if you want to fetch permissions dynamically from an API, you can simply replace the hardcoded roleAccess and rolePermissions with values coming from your backend.
This makes your admin panel more secure, maintainable, and scalable.
Happy coding 🚀

Top comments (0)