Menu Rules

XinAdmin implements user authentication and authorization based on Laravel Sanctum. Every interface, menu, page, and button in the application can be considered as a Sanctum ability, and access permissions are controlled through Menu Rules.

Core Features

  • Controller Attribute Route Permission Verification - Automatically verifies user permissions through route annotations
  • Dynamic Menus - Dynamically generates menus based on user roles
  • Page Button Permission Verification - Controls page element display based on user permissions

Database Structure

sys_rule - Rules Table

Stores all system permission rules, including menu, route, and button-level permissions.

FieldTypeDescription
idbigintRule ID
parent_idbigintParent ID, top-level menu is 0
typevarcharRule type: menu/route/rule
namevarcharRule name
keyvarcharUnique identifier, used for permission verification
pathvarcharRoute path, menu path is used as prefix route
iconvarcharIcon name
localvarchari18n key
linktinyintIs external link: 0-No, 1-Yes
orderintSort order
statustinyintStatus: 0-Normal, 1-Disabled
hiddentinyintIs hidden: 0-Show, 1-Hide

type Rule Type Description:

TypeDescription
menuMenu type, used for sidebar menu display
routeRoute type, corresponds to specific pages, generates route rules
rulePermission type, used for fine-grained permission control (e.g., button permissions)

sys_role - Roles Table

FieldTypeDescription
idbigintRole ID
namevarcharRole name
sortintSort order
descriptionvarcharRole description
statustinyintStatus: 0-Normal, 1-Disabled

sys_role_rule - Role Permission Pivot Table

FieldTypeDescription
role_idbigintRole ID
rule_idbigintRule ID

Permission Type Details

Used for sidebar menu display, corresponding to system menu structure.

// Menu type example
[
    'id' => 1,
    'parent_id' => 0,
    'type' => 'menu',
    'name' => 'System Management',
    'key' => 'system',
    'path' => '/admin/system',
    'icon' => 'SettingOutlined',
    'order' => 1,
    'status' => 0,
    'hidden' => 0,
]

Route Type (route)

Corresponds to specific pages or functions, generates actual route rules and participates in permission verification.

// Route type example
[
    'id' => 2,
    'parent_id' => 1,
    'type' => 'route',
    'name' => 'User Management',
    'key' => 'system.user',
    'path' => '/admin/system/user',
    'local' => 'menu.system.user',
    'order' => 1,
    'status' => 0,
    'hidden' => 0,
]

Permission Type (rule)

Used for fine-grained permission control, such as button permissions within a page.

// Permission type example
[
    'id' => 10,
    'parent_id' => 2,
    'type' => 'rule',
    'name' => 'Export Users',
    'key' => 'system.user.export',
    'order' => 1,
    'status' => 0,
]

Permission Verification Flow

Backend Permission Verification

  1. When user logs in, the system retrieves all user permission keys through SysUserService::ruleKeys()
  2. When user accesses an endpoint, authorize middleware verifies if the request has corresponding permissions
  3. The authorize parameter in route annotations is matched against permission keys
// Controller example
#[RequestAttribute(
    routePrefix: '/admin/user',
    abilitiesPrefix: 'admin'
)]
class UserController extends Controller
{
    // Required permission: admin.user.create
    #[PostRoute('/create', 'user.create')]
    public function create(): JsonResponse { }

    // Required permission: admin.user.update
    #[PutRoute('/{id}', 'user.update')]
    public function update(int $id): JsonResponse { }
}

Super Administrator

The account with user ID 1 is the super administrator and has all permissions.

// Logic in SysUserService
if ($id == 1) {
    // Super administrator has all permissions
    return SysRuleModel::query()->where('status', 1)->pluck('key')->toArray();
}

Dynamic Menu Generation

Backend Implementation

After user logs in, call /system/user/menu endpoint to get menu data:

// SysUserController::menu()
public function menu(): JsonResponse
{
    $id = Auth::id();
    $menus = $this->service->getAdminMenus($id);
    return $this->success(compact('menus'));
}

Get User Menu Logic

public function getAdminMenus(int $id): array
{
    // Super administrator gets all menus
    if ($id == 1) {
        $menus = SysRuleModel::query()
            ->where('status', 1)
            ->whereIn('type', ['menu', 'route'])
            ->get()
            ->toArray();
    } else {
        // Regular users get menus through roles
        $roles = SysUserModel::with(['roles.rules' => function ($query) {
            $query->where('status', 1)
                  ->whereIn('type', ['menu', 'route']);
        }])->find($id)->roles;

        $menus = collect($roles)
            ->map(fn ($item) => $item['rules'])
            ->collapse()
            ->unique('id')
            ->toArray();
    }
    return $this->getTreeData($menus);
}

Frontend Menu Rendering

Frontend manages menu state through useMenuStore:

// Get menus
const menus = await menu();

Menu rendering filters hidden items:

const transformMenus = (nodes: IMenus[], t: any): MenuItem[] => {
  return nodes.reduce<MenuItem[]>((acc, node) => {
    // Only process route and menu types that are not hidden
    if (!['route', 'menu'].includes(node.type!) || !node.hidden) {
      return acc;
    }
    // Build menu item...
  }, []);
};

Permission Middleware

abilities Middleware

Verifies if user has specific permission ability:

// Usage
$middleware[] = 'abilities:' . $authorize;

// Example: abilities:admin.user.create

authGuard Middleware

Verifies if user is authenticated:

$middleware[] = 'auth:sanctum';
$middleware[] = 'authGuard';  // Or with parameter authGuard:admin

Management Page Features

  • Menu List: Tree display of all menu rules
  • Add Rule: Supports adding menu, route, and permission types
  • Edit Rule: Modify rule information
  • Delete Rule: Delete rule (requires confirmation)
  • Set Display Status: Control whether menu is displayed in sidebar
  • Set Enable Status: Control whether rule is active

Add Rule Examples

Add Top-level Menu:

Parent Menu: Top-level Menu
Type: Menu
Name: Content Management
Unique Key: content
Sort: 10

Add Sub-level Route:

Parent Menu: Content Management
Type: Route
Name: Article Management
Unique Key: content.article
Route Path: /admin/content/article
i18n: menu.content.article
Sort: 1

Add Button Permission:

Parent Menu: Article Management
Type: Permission
Name: Audit Article
Unique Key: content.article.audit
Sort: 1

Relationship with Attribute Routing

Menu rules work with attribute routing. Permission key composition rules:

ComponentabilitiesPrefixauthorizeFinal Permission Key
RequestAttributeadmin--
Create-createadmin.create
Update-updateadmin.update
Query-queryadmin.query

The key field in menu rules must match the permission key generated by attribute routing:

// Controller
#[RequestAttribute(
    routePrefix: '/admin/user',
    abilitiesPrefix: 'admin'
)]
class UserController extends Controller
{
    #[PostRoute('/create', 'user.create')]
    public function create() { }
    // Permission: admin.user.create
}

// Corresponding rule in menu rules table
['key' => 'admin.user.create', 'name' => 'Create User']

Best Practices

1. Permission Key Naming Convention

Recommended module.resource.operation naming:

system.user.list      - User List
system.user.create    - Create User
system.user.update    - Update User
system.user.delete    - Delete User
system.user.export    - Export User

2. Role Permission Assignment

Recommended to create multiple roles, each with different permission combinations:

  • Super Administrator: Has all permissions
  • Operations Administrator: Content management related permissions
  • Regular Administrator: Basic view permissions

3. Button-level Permission Control

For sensitive operations within a page (such as delete, audit), use rule type permission:

// React component showing button based on permission
{hasPermission('content.article.audit') && (
    <Button onClick={handleAudit}>Audit</Button>
)}