Implementing modified role based access control in Laravel
ColoredCow
April 10, 2017

Access control is an important aspect of any system dealing with sensitive content. At ColoredCow, we recently worked on a healthcare system and one of its key requirement was having an ACL system to enable secure and efficient handling of data.

 

Key specifications that we identified from the requirements that we received with regards to ACL in our application were-

  • User roles as the basis of defining a user’s ability to access applications components.
  • User level as a way of representing hierarchy inside the organisation.
  • System to respond differently to users with same role but on different organisation level.
  • Application should support multiple organisations.

 

We opted for a modified form of Role Based Access Control in our application as it allowed us to make use of user roles as a basis of defining access to the application components and then incorporating the additional access controls based on user levels and the existence of multiple organisations in the system.

 

For this purpose we defined different roles in our application and attached different permissions to each role. We also created different user levels which was analogous to the organisation’s hierarchy. All users could have any number of roles but were allowed to exist in only one level. Based on the combination of user-level and user-roles we could control the user’s access to the system. A user at a higher user-level could access all functionalities of a user at a lower level to him having the same roles and belonging to the same organisation.

 

Once all the blocks have been identified and worked out, came the next part of integrating ACL into the applications architecture. We were building our application on the Laravel framework, which provided great options to implement authorisation out of the box. To implement our modified form of role based ACL, we had to opt for a combination of the components available.

 

The first level of access control was done on the application routes. Laravel provides a feature known as ‘Middlewares’, which allows the application to perform filtering of incoming request before they hit the application logic. Using this, we were able to impose the user role and level* based restriction on routes. The sample code implementation is illustrated below-

(*user levels were declared as integers in descending order of their place in hierarchy)

Middleware check on route-

Route::group(['middleware' => 'role:admin-user', config('constants.user-level.local_admin')], function () {

//Application Routes following the above constraints

});

Function in middleware to check permissions-

public function handle($request, Closure $next, $role, $userLevel = config('constants.user-level.super_admin'))
{
    if (session('user-level') == config('constants.user-level.super_admin')) {
        return $next($request);
    }

    if (!$request->user()->hasRole($role) && session('user-level') >= $userLevel) {
        $request->session()->flash('failure', 'Unauthorized Access!');
        return redirect('/home');
    }

    return $next($request);
}

 

The middleware based access control provided us with a coarse level of control, specifying the ability of a user to access the functionalities available in the system. However, activities like modification/updation of resources need a finer level of control as even when a user has the roles and permission required to perform them, his access might be limited to the resources belonging to his own organisation. For this, we made use of a feature provided by Laravel, know as Policies, which is a way to specify detailed access control over a particular resource. Every request for updation in resources has to go through a policy check to ascertain if the requesting user is authorised to perform the activity on the requested resource. A sample has been illustrated below-

public function destroy(Request $request)
{

    $patient_id = $request->input('patient_id');
    if (!policy(new Patient)->canDelete($patient_id)) {
        session()->flash('failure', 'Unauthorized Access!');
        return redirect('/home');
    }

    //Application Logic

}

 

Policy based restriction enabled us to restrict the access to a particular resource. However, in certain cases, like searching, we work on a large collection of resources (database). In this case we need to specify a boundary within the database to which the user is allowed access for performing his operations. We implemented this by specifying additional constraint on the search functions based on the user’s role and level. A sample implementation is show below-

public static function search($filters)
{
    //Search Logic

    if (session('user-level') == config('constants.user-level.local_admin')) {
        
        //Additional table joins and restriction for user of this type

    } elseif (session('user-level') > config('constants.user-level.local_admin')) {

       //Additional table joins and restriction for user of this type
      //This search is more restrictive compared to previous user

    }

    //Search Logic
}

 

Using the components listed above, we were able to implement the access control solution that met the specifications required in our application.