rustzen-admin: Frontend Permission System Design and Implementation Based on React Router


This article delves into the design and implementation of a modern frontend permission system based on React Router, demonstrating how to build a complete frontend permission control solution through declarative components, permission matching, and dynamic menu generation.

1. System Architecture Overview

πŸ”„ Complete Permission Control Flow

graph TD
    A[User Login] --> B[Get User Permissions]
    B --> C[Permission State Management]
    C --> D[Route Permission Check]
    D --> E[Component Permission Control]
    E --> F[Dynamic Menu Generation]
    F --> G[User Interface Rendering]
    H[Permission Change] --> I[Clear Permission Cache]
    I --> J[Re-fetch Permissions]
    J --> C

πŸ—οΈ Frontend Permission Architecture Design

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                Frontend Permission System Architecture      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ Route Perms β”‚  β”‚ Component   β”‚  β”‚ Button      β”‚         β”‚
β”‚  β”‚ AuthGuard   β”‚  β”‚ AuthWrap    β”‚  β”‚AuthConfirm  β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ State Mgmt  β”‚  β”‚ Permission  β”‚  β”‚ Dynamic     β”‚         β”‚
β”‚  β”‚useAuthStore β”‚  β”‚checkPermissionsβ”‚ β”‚getMenuData β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ Permission  β”‚  β”‚ Path        β”‚  β”‚ Wildcard    β”‚         β”‚
β”‚  β”‚ Storage     β”‚  β”‚formatPathCodeβ”‚ β”‚ Support     β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Permission State Management Design

2.1 Zustand State Management Architecture

Design Philosophy: Centralized permission state management with persistence and real-time updates

interface AuthState {
  userInfo: Auth.UserInfoResponse | null;
  token: string | null;
  updateUserInfo: (params: Auth.UserInfoResponse) => void;
  updateAvatar: (avatarUrl: string) => void;
  updateToken: (params: string) => void;
  setAuth: (params: Auth.LoginResponse) => void;
  clearAuth: () => void;
  checkPermissions: (code: string) => boolean;
  checkMenuPermissions: (path: string) => boolean;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      userInfo: null,
      token: null,

      // Update user information
      updateUserInfo: (params) => set({ userInfo: params }),

      // Update avatar
      updateAvatar: (avatarUrl) =>
        set((state) => ({
          userInfo: state.userInfo ? { ...state.userInfo, avatarUrl } : null,
        })),

      // Update token
      updateToken: (params) => set({ token: params }),

      // Set authentication information
      setAuth: (params) =>
        set({
          userInfo: params.user_info,
          token: params.token,
        }),

      // Clear authentication information
      clearAuth: () => set({ userInfo: null, token: null }),

      // Core permission check logic
      checkPermissions: (code: string) => {
        const permissions = get().userInfo?.permissions || [];

        // Return false if no permissions
        if (permissions.length === 0) {
          return false;
        }

        // Super admin permissions
        if (permissions.includes("*")) {
          return true;
        }

        // Exact match
        if (permissions.includes(code)) {
          return true;
        }

        // Wildcard match: system:user:* -> system:*
        const codeArr = code.split(":");
        for (let i = codeArr.length - 1; i > 0; i--) {
          const prefix = codeArr.slice(0, i).join(":") + ":*";
          if (permissions.includes(prefix)) {
            return true;
          }
        }
        return false;
      },

      // Menu permission check
      checkMenuPermissions: (path: string) => {
        const code = formatPathCode(path);
        return get().checkPermissions(code);
      },
    }),
    {
      name: "auth-store", // Persistent storage
    }
  )
);

2.2 Permission Matching Specification

Core Points: Support for exact matching, wildcard matching, and hierarchical permission inheritance

User PermissionsCheck PermissionMatch ResultDescription
["*"]system:user:createβœ…Super administrator
["system:user:create"]system:user:createβœ…Exact match
["system:user:*"]system:user:createβœ…Wildcard match
["system:*"]system:user:createβœ…Hierarchical inheritance
["system:role:*"]system:user:create❌No match

2.3 Path to Permission Code Conversion

Design Highlights: Automatically convert route paths to permission codes

const formatPathCode = (pathname: string) => {
  const code = pathname.replace(/\//g, ":").slice(1);

  // Create page: /system/user/create -> system:user:create
  if (code.endsWith(":create")) {
    return code;
  }

  // Edit/Detail page: /system/user/123/edit -> system:user:edit
  if (code.endsWith(":edit") || code.endsWith(":detail")) {
    return code
      .split(":")
      .filter((s) => !/^\d+$/.test(s)) // Filter numeric IDs
      .join(":");
  }

  // List page: /system/user -> system:user:list
  return `${code}:list`;
};

Path Conversion Examples:

Route PathPermission CodeDescription
/system/usersystem:user:listUser list page
/system/user/createsystem:user:createCreate user page
/system/user/123/editsystem:user:editEdit user page
/system/user/123/detailsystem:user:detailUser detail page

3. Permission Component System Design

3.1 AuthGuard - Route Guard Component

Design Philosophy: Unified handling of route-level permission verification with automatic redirection

interface AuthGuardProps {
  children: React.ReactNode;
}

export const AuthGuard: React.FC<AuthGuardProps> = ({ children }) => {
  const location = useLocation();
  const { token, updateUserInfo, checkMenuPermissions } = useAuthStore();
  const { data: userInfo } = useSWR("getUserInfo", authAPI.getUserInfo);

  // Automatically update user information
  useEffect(() => {
    if (userInfo) {
      updateUserInfo(userInfo);
    }
  }, [userInfo, updateUserInfo]);

  // Redirect to login page if not logged in
  if (!token) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  // Allow home page directly
  if (location.pathname === "/") {
    return children;
  }

  // Permission check
  const isPermission = checkMenuPermissions(location.pathname);
  return isPermission ? children : <Navigate to="/403" replace />;
};

Usage:

export const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <AuthGuard>
        <BasicLayout />
      </AuthGuard>
    ),
    children: [
      {
        path: "system",
        children: [
          {
            path: "user",
            element: <UserPage />,
          },
          {
            path: "role",
            element: <RolePage />,
          },
        ],
      },
    ],
  },
]);

3.2 AuthWrap - Component-level Permission Control

Design Highlights: Declarative permission control with support for hiding and fallback handling

interface AuthWrapProps {
  code: string;
  children: React.ReactNode;
  hidden?: boolean;
  fallback?: React.ReactNode;
}

export const AuthWrap: React.FC<AuthWrapProps> = ({
  code,
  children,
  hidden = false,
  fallback = null,
}) => {
  const isPermission = useAuthStore.getState().checkPermissions(code);

  if (isPermission && !hidden) {
    return children;
  }

  return fallback;
};

Usage Examples:

// Basic permission control
<AuthWrap code="system:user:create">
  <Button type="primary">Add User</Button>
</AuthWrap>

// Permission control with fallback handling
<AuthWrap
  code="system:user:edit"
  fallback={<span style={{ color: '#999' }}>No edit permission</span>}
>
  <Button>Edit User</Button>
</AuthWrap>

// Permission control with status check
<AuthWrap
  code="system:user:edit"
  hidden={record.status === 'disabled'}
>
  <Button>Edit User</Button>
</AuthWrap>

3.3 AuthConfirm - Operation-level Permission Control

Design Philosophy: Dangerous operations require permission confirmation for enhanced security

interface AuthConfirmProps extends AuthWrapProps {
  title: React.ReactNode;
  description?: React.ReactNode;
  onConfirm: () => Promise<void>;
  onCancel?: () => Promise<void>;
}

export const AuthConfirm: React.FC<AuthConfirmProps> = (props) => {
  const handleConfirm = () => {
    modalApi.confirm({
      title: props.title,
      content: props.description,
      onOk: props.onConfirm,
      onCancel: props.onCancel,
    });
  };

  return (
    <AuthWrap code={props.code} hidden={props.hidden}>
      <span onClick={handleConfirm} className={props.className}>
        {props.children}
      </span>
    </AuthWrap>
  );
};

Usage Example:

<AuthConfirm
  code="system:user:delete"
  title="Confirm Delete User"
  description="This action cannot be undone. Are you sure you want to delete this user?"
  onConfirm={handleDelete}
>
  <Button danger>Delete User</Button>
</AuthConfirm>

3.4 AuthPopconfirm - Popover Confirmation Permission Control

Design Highlights: Lightweight confirmation operations suitable for frequently used scenarios

interface AuthPopconfirmProps extends AuthWrapProps {
  title: React.ReactNode;
  description?: React.ReactNode;
  onConfirm: () => Promise<void>;
  onCancel?: () => Promise<void>;
}

export const AuthPopconfirm: React.FC<AuthPopconfirmProps> = ({
  code,
  children,
  hidden = false,
  title,
  description,
  onConfirm,
  onCancel,
}) => {
  return (
    <AuthWrap code={code} hidden={hidden}>
      <Popconfirm
        placement="leftBottom"
        title={title}
        description={description}
        onConfirm={onConfirm}
        onCancel={onCancel}
      >
        {children}
      </Popconfirm>
    </AuthWrap>
  );
};

4. Dynamic Menu System Design

4.1 Menu Data Structure

Design Philosophy: Support for hierarchical menus and permission control

export type AppRouter = RouteObject & {
  name?: string;
  icon?: React.ReactNode;
  children?: AppRouter[];
};

4.2 Dynamic Menu Generation

Core Points: Automatically generate menu structure based on user permissions

export const getMenuData = (): AppRouter[] => {
  const { checkMenuPermissions } = useAuthStore.getState();

  const getMenuList = (menuList: AppRouter[]): AppRouter[] => {
    return menuList
      .filter((item) => {
        // Menu items without paths (like group titles) are not shown as clickable
        if (!item.path) return false;
        // Parent menus with children are displayed directly
        if (item.children) return true;
        // Check page permissions
        return checkMenuPermissions(item.path);
      })
      .map((item) => {
        return {
          ...item,
          children: item.children ? getMenuList(item.children) : undefined,
        } as AppRouter;
      })
      .filter((item) => {
        // Filter out parent menus without children
        if (item.children?.length === 0) {
          return false;
        }
        return true;
      });
  };

  return getMenuList(pageRoutes);
};

5. Permission Code Standards and Best Practices

5.1 Permission Naming Standards

Design Principles: Unified, clear, and extensible permission naming

Module:Feature:Operation
β”œβ”€β”€ system:user:list     # User list
β”œβ”€β”€ system:user:create   # Create user
β”œβ”€β”€ system:user:edit     # Edit user
β”œβ”€β”€ system:user:delete   # Delete user
β”œβ”€β”€ system:role:*        # All role management permissions
└── admin:*              # All admin permissions

5.2 Component Usage Standards

ScenarioComponentExampleDescription
Page access controlAuthGuardRoute-level permissionControl access to the entire page
Feature module displayAuthWrapButtons, forms, etc.Control component show/hide
Dangerous operation confirmAuthConfirmDelete, export operationsOperations requiring secondary confirmation
Lightweight confirmationAuthPopconfirmCommon operation confirmFrequently used confirmation operations

6. Summary

🎯 Multiple Benefits from Core Design

Declarative Permission Control Design:

Permission Matching Mechanism:

Componentized Permission System:


🧭 Final Thoughts

This frontend permission system is based on my real-world practice during the development of rustzen-admin, evolving from initial scattered permission checks to final declarative permission control. Through layered permission control, permission matching, and componentized design, we’ve achieved a frontend permission management solution that is both secure and user-friendly.


πŸ“Ž Related Resources: