Authorization (RBAC)
XinAdmin implements a standard User-Role-Permission three-tier RBAC authorization model.
RBAC Model
┌──────────┐ many-to-many ┌──────────┐ many-to-many ┌──────────────┐
│ SysUser │ <──────────────>│ SysRole │ <──────────────>│ SysRule │
│ (User) │ sys_user_role │ (Role) │ sys_role_rule │ (Permission) │
└──────────┘ └──────────┘ └──────────────┘
Database Tables
Permission Rule Types
Permission Retrieval Logic
// SysUserModel::access() - Get all permission keys for a user
public function access()
{
// Super admin (ID=1): return all permissions
if ($this->id === 1) {
return SysRuleModel::pluck('key')->toArray();
}
// Regular user: retrieve via role-permission relationships
return $this->roles()
->with('rules')
->get()
->pluck('rules.*.key')
->flatten()
->unique()
->toArray();
}
Super Admin
User ID = 1 is hardcoded as the super admin with the following privileges:
- Automatically receives all permission keys
- Always passes token ability checks
- Cannot be deleted
- Automatically gets the full menu tree
- Role ID = 1 permissions cannot be modified
Super admin privileges are implemented at three levels: SysUserModel::access() returns all keys, SysRoleModel::getRuleIdsAttribute() returns all rules, and SysAccessToken::can() always returns true.
After login, the backend returns the menu tree based on the user's role permissions:
- Super admin: directly returns all
status=1 menus/routes
- Regular user: retrieves menus through the role → permission chain, builds a tree structure, and deduplicates
The frontend LayoutContext calls the menu() API on mount; MenuRender renders navigation based on menu type and hidden status.
Frontend Permission Control
Conditional rendering component based on permission key:
<AuthButton auth="system.user.create">
<Button type="primary">Create User</Button>
</AuthButton>
When the user lacks permission, the component returns null and the button is not rendered. Implementation:
// AuthButton core logic
const access = useAuthStore(state => state.access);
if (!auth) return <>{children}</>;
if (!access.includes(auth)) return null;
return <>{children}</>;
useAuth Hook
Manual permission checking in component logic:
import useAuth from '@/hooks/useAuth';
function MyComponent() {
const { auth } = useAuth();
return (
<>
{/* Show only when authorized */}
{auth('system.user.delete') && <Button danger>Delete</Button>}
{/* Enable control only when authorized */}
<Switch disabled={!auth('system.user.update')} />
</>
);
}
XinTable accessName
The accessName prop on XinTable auto-generates permission keys for operation buttons:
<XinTable accessName="system.user" ... />
// Auto-generates these permission controls:
// Add button → system.user.create
// Edit button → system.user.update
// Delete button → system.user.delete
Permission Key Naming Convention
Use dot-separated hierarchical naming:
{module}.{submodule}.{action}
Examples:
system.user.query # System → Users → Query
system.user.create # System → Users → Create
system.role.update # System → Roles → Edit
file.manage.delete # File Management → Delete