• Home
  • Skills
  • Projects
  • Blog
  • Contact
Resume
Naser Rasouli

Author

Naser Rasouli

Front-End developer - sharing lessons learned, notes, and write-ups from real projects.

GitHubLinkedIn

Last posts

Mastering Record in TypeScript
2025-12-13•1 min read

Mastering Record in TypeScript

Mastering Record in TypeScript: The Clean Way to Map Enums to Labels and Colors

Conventional Commits: Write Better Git Messages
2025-12-12•1 min read

Conventional Commits: Write Better Git Messages

Master Git commit messages with Conventional Commits to keep history readable and automation-friendly.

Mastering Git Flow
2025-12-08•1 min read

Mastering Git Flow

Mastering Git Flow: A Developer’s Guide to Branching Strategies

Static RBAC: Roles & Permissions in React

Static RBAC: Roles & Permissions in React

2025-12-20
reactjsrbacaccess-controlpermissions

Introduction

Building an admin panel means deciding who can see or do what. A simple static approach — roles and permissions defined in code, not fetched from an API — is often enough for small/medium projects. Here’s a clean pattern to guard both routes and UI controls, with room to grow later.

What we’ll cover

  • Define roles, pages, and permissions in one place.
  • Protect routes so only allowed roles can visit them.
  • Hide or show UI actions based on permissions.

1) Define roles and permissions

Keep everything in a single roles.ts to avoid scattered logic.

// 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"],
  [ROLES.VIEWER]: ["dashboard"],
};

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

You now have one source of truth: which role can visit which pages, and which actions each role can perform.


2) Route-level access with ProtectedRoute

Block navigation if the user’s role isn’t allowed.

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

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

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

  return children;
}

Use it in your routes:

// 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>
    ),
  },
];

Only roles declared in roleAccess can reach each page.


3) Component-level access with AccessControl

Hide or show specific UI actions based on permissions.

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

type Props = {
  permission: string;
  children: ReactNode;
};

export function AccessControl({ permission, children }: Props) {
  const { role } = useAuth();
  if (!role) return null;

  const permissions = rolePermissions[role] || [];
  return permissions.includes(permission) ? <>{children}</> : null;
}

Example usage 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>
  );
}

Summary

  • Define roles, routes, and permissions in one module (roles.ts).
  • Use ProtectedRoute to guard navigation.
  • Use AccessControl to gate UI actions.
  • Start static; later you can swap roleAccess and rolePermissions with API-driven data.

Conclusion

With a few lightweight helpers, you get predictable, centralized access control that keeps your admin UI clean today and flexible enough to go dynamic tomorrow. Happy coding! 🚀