0
0
Lập trình
Admin Team
Admin Teamtechmely

Kiểm Soát Truy Cập Dựa Trên Vai Trò & Quyền Trong React

Đăng vào 4 tháng trước

• 5 phút đọc

Giới thiệu

Khi xây dựng một bảng điều khiển quản trị, một trong những mối quan tâm quan trọng nhất là cách xử lý kiểm soát truy cập. Không phải người dùng nào cũng nên có quyền xem hoặc thực hiện mọi hành động. Ví dụ, quản trị viên có thể tạo và xóa người dùng, trong khi biên tập viên chỉ có thể xem và cập nhật, và người xem chỉ được phép xem thông tin mà không được thực hiện thay đổi nào.

Có nhiều cách để triển khai kiểm soát truy cập, nhưng trong bài viết này, chúng ta sẽ tập trung vào một phương pháp đơn giản và tĩnh. Điều này có nghĩa là vai trò và quyền được định nghĩa trong mã nguồn (không lấy từ API), thường là đủ cho các dự án nhỏ đến vừa hoặc khi các quy tắc truy cập của bạn không thay đổi thường xuyên.

Nội dung chính

Chúng ta sẽ chia bài viết thành hai phần:

  1. Kiểm soát truy cập ở cấp độ tuyến đường: đảm bảo rằng người dùng chỉ có thể điều hướng đến các trang mà họ được phép.
  2. Kiểm soát truy cập ở cấp độ thành phần: hiển thị hoặc ẩn các nút, menu hoặc tính năng cụ thể bên trong một trang tùy thuộc vào quyền hạn.

Phương pháp này giữ cho dự án của bạn sạch sẽ, có thể mở rộng và sẵn sàng để mở rộng nếu sau này bạn quyết định lấy quyền từ backend một cách động.

1. Định nghĩa Vai trò và Quyền

Bước đầu tiên là định nghĩa các vai trò và quyền liên quan. Vì chúng ta đang làm việc với kiểm soát truy cập tĩnh, chúng ta có thể mã hóa chúng trong một tệp roles.ts như sau:

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

Trong đoạn mã trên, chúng ta đã định nghĩa:

  • ROLES: các loại người dùng (quản trị viên, biên tập viên, người xem).
  • PERMISSIONS: các hành động có thể được phép (tạo, xóa, xem).
  • roleAccess: các trang mà mỗi vai trò có thể truy cập.
  • rolePermissions: các hành động mà mỗi vai trò có thể thực hiện.

2. Kiểm Soát Truy Cập Ở Cấp Độ Tuyến Đường

Chúng ta cần ngăn chặn người dùng không có quyền truy cập vào một số tuyến đường nhất định. Ví dụ, một người xem không nên mở được trang quản lý người dùng.
Chúng ta có thể xây dựng một thành phần ProtectedRoute kiểm tra xem vai trò của người dùng hiện tại có nằm trong danh sách cho phép hay không. Nếu không, nó sẽ chuyển hướng họ đến một trang "không được phép".

typescript Copy
// 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;
};

Và chúng ta có thể sử dụng nó trong cấu hình tuyến đường như sau:

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

Bây giờ, chỉ những vai trò được liệt kê trong roleAccess mới có thể truy cập các tuyến đường này.

3. Kiểm Soát Truy Cập Ở Cấp Độ Thành Phần

Kiểm soát ở cấp độ tuyến đường không phải lúc nào cũng đủ. Thường thì bạn sẽ muốn ẩn hoặc hiển thị các yếu tố UI cụ thể bên trong một trang tùy thuộc vào quyền hạn của người dùng. Ví dụ, chỉ có quản trị viên mới thấy nút "Xóa người dùng".
Chúng ta có thể tạo một thành phần AccessControl kiểm tra quyền cụ thể.

typescript Copy
// 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;
};

Ví dụ sử dụng bên trong một trang:

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

function UsersPage() {
  return (
    <div>
      <h1>Danh Sách Người Dùng</h1>

      <AccessControl permission={PERMISSIONS.USER_CREATE}>
        <button>Thêm Người Dùng</button>
      </AccessControl>

      <AccessControl permission={PERMISSIONS.USER_DELETE}>
        <button>Xóa Người Dùng</button>
      </AccessControl>
    </div>
  );
}

Kết luận

Với chỉ một vài bước đơn giản, chúng ta đã tạo ra một hệ thống kiểm soát truy cập tĩnh sạch sẽ trong React:

  • ProtectedRoute xử lý truy cập ở cấp độ tuyến đường.
  • AccessControl xử lý truy cập ở cấp độ UI.
  • Các vai trò và quyền được định nghĩa ở một nơi, giúp dễ dàng quản lý.

Phương pháp này hoàn hảo cho các dự án mà vai trò và quyền không thay đổi thường xuyên. Sau này, nếu bạn muốn lấy quyền một cách động từ API, bạn chỉ cần thay thế roleAccessrolePermissions được mã hóa bằng các giá trị đến từ backend của bạn.
Điều này giúp bảng điều khiển quản trị của bạn an toàn hơn, dễ bảo trì và có thể mở rộng.

Những Lưu Ý Quan Trọng:

  • Đảm bảo kiểm tra kỹ các quyền được cấp cho từng vai trò.
  • Theo dõi và cập nhật quyền khi có thay đổi trong tổ chức.

Chúc bạn lập trình vui vẻ! 🚀

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào