Custom Role-Based Access Control (RBAC) in Laravel Nova: a handy implementation example

--

In Laravel applications, implementing a robust role-based access control (RBAC) system is crucial for managing permissions effectively. While Laravel offers powerful tools like policies for authorization, custom implementations are often necessary to tailor access control to specific project requirements. In this comprehensive guide, we’ll delve into the process of implementing a custom RBAC system using Laravel’s policies, with a focus on restricting actions within Laravel Nova, a powerful administration panel.

Understanding Role-Based Access Control

Role-Based Access Control (RBAC) is a method for regulating access to resources based on the roles assigned to users within an application. In Laravel, roles are typically associated with permissions, which determine what actions a user can perform. By leveraging roles and permissions, administrators can finely tune access control to match their application’s needs.

Press control to activate… role-based access control! Image credits: charlesdeluvio on Unsplash

Step 1: Setting Up the Database

A well-designed database structure is fundamental for implementing RBAC effectively. Let’s start by creating the necessary tables for roles, permissions, and their relationships using Laravel migrations.

1.1. Roles Table Migration

php artisan make:migration create_roles_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRolesTable extends Migration
{
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('roles');
}
}

1.2 Permissions Table Migration

php artisan make:migration create_permissions_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePermissionsTable extends Migration
{
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('permissions');
}
}

1.3. Role-Permission Relationship Table Migration

php artisan make:migration create_role_permission_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRolePermissionTable extends Migration
{
public function up()
{
Schema::create('role_permission', function (Blueprint $table) {
$table->unsignedInteger('role_id');
$table->unsignedInteger('permission_id');
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
$table->primary(['role_id', 'permission_id']);
});
}

public function down()
{
Schema::dropIfExists('role_permission');
}
}

1.4. Modules Table Migration

php artisan make:migration create_modules_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateModulesTable extends Migration
{
public function up()
{
Schema::create('modules', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('modules');
}
}

Run the migration to create the tables:

php artisan migrate

Step 2: Implementing Custom Policies

Laravel’s policy system enables developers to define authorization logic for model actions. We’ll create two policy classes: RolePolicy and PermissionPolicy, each containing methods for various actions related to roles and permissions. These methods determine whether the authenticated user possesses the necessary permissions to perform the requested action.

// RolePolicy class
class RolePolicy {
// Authorization methods for role actions
public function viewAny(User $user)
{
return $user->hasPermission('view-any-permissions');
}

public function view(User $user, Permission $permission)
{
return $user->hasPermission('view-permissions');
}

}

// PermissionPolicy class

class PermissionPolicy {
// Authorization methods for permission actions
public function viewAny(User $user)
{
return $user->hasPermission('view-any-roles');
}

public function view(User $user, Role $role)
{
return $user->hasPermission('view-roles');
}
}

Step 3: Seeding Permissions

Populating the database with initial permissions is essential. Below is a snippet from a permission seeder that defines permissions and associates them with modules. This seeding process ensures that the application starts with a predefined set of permissions.

foreach ($permissions as $key => $permission) {
$module = Module::with('permissions')->where('name', $key)->first();

if (!$module) {
$module = Module::create([
'name' => $key
]);
}

foreach ($permission as $permissionName) {
$permissionAlreadyExists = Permission::where('name', $permissionName)->first();

if (!in_array($permissionName, $module->permissions->pluck('name')->toArray()) && !$permissionAlreadyExists) {
Permission::create([
'name' => $permissionName,
'module_id' => $module->id
]);
}
}

$module->permissions()->whereNotIn('name', $permission)->delete();
}
Seed your permissions right, even in places where you don’t expect them to be needed. Image credits: Osama Khan on Unsplash

Step 4: Integrating RBAC with Laravel Nova

With policies and database structures in place, integrating RBAC into Laravel Nova is straightforward. We can utilize Nova’s authorization features to restrict access to resources based on roles and permissions. By applying the appropriate policies to Nova resources, we ensure that only authorized users can perform specific actions within the administration panel.

Step 5: Implementing RBAC in the Auth Service Provider

To apply RBAC globally in our application, we’ll utilize Laravel’s Gate service within the boot method of the AuthServiceProvider. Here’s an example of how to define permissions:

public function boot()
{
Gate::before(function ($user, $ability) {
return $user->is_super_admin ? true : null;
});

// Define permissions
Gate::define('company.manage-members', CompanyMembersPolicy::class .
'@canManageCompanyMembers');
Gate::define('company.manage', CompanyManage::class . '@canManageCompany');
Gate::define('plan.view', PlanPolicy::class . '@canView');
Gate::define('plan.update', PlanPolicy::class . '@canUpdate');
Gate::define('plan.delete', PlanPolicy::class . '@canDelete');
Gate::define('company.viewMember', CompanyPolicy::class . '@viewMember');
Gate::define('company.viewOwner', CompanyPolicy::class . '@viewOwner');
Gate::define('company.viewOwnerWithReader', CompanyPolicy::class . '@viewOwnerWithReader');
Gate::define('company.viewTemporaryOwner', CompanyPolicy::class . '@viewTemporaryOwner');
Gate::define('company.inCompany', CompanyPolicy::class . '@inCompany');
}

Conclusion

Custom role-based access control enhances the security and integrity of Laravel applications. Leveraging Laravel’s policy system and thoughtful database design, developers can implement fine-grained access control tailored to their project’s requirements. By carefully managing roles and permissions, administrators have the tools they need to maintain a secure and efficient application environment.

By following the steps outlined in this guide, you can implement a robust RBAC system in your Laravel application, providing granular control over user access and permissions. With customization options and flexibility offered by Laravel’s policies, you can ensure that your application’s access control aligns perfectly with your business logic and security requirements.

RBAC is the key. Image credits: Peachaya Tanomsup on Vecteezy

Blog by Riccardo Vincelli and Umer Waqas brought to you by the engineering team at Sharesquare.

--

--

Sharesquare.co engineering blog by R. Vincelli

This is the Sharesquare.co engineering blog, brought to you by Riccardo Vincelli, CTO at Sharesquare. Real-life engineering tales from the crypt!