Are you sure you need entrust or laravel-permission to implement your authorization?

October 16, 2018

"Hmmm, I need some basic authorization, like admin role for admin panel and maybe some editor/moderator role... Let's google it. Wow! Laravel already has packages for that! zizaco/entrust, spatie/laravel-permission and others! Let's choose one!"

That's how it usually happens. Then package's migration will add about 5 tables to store roles, permissions and their relations. All authorization rules will be stored there, like 'editor' and 'admin' roles will have 'edit-posts' permission. Usually, there are lot of database copies in project. Developer copies, maybe some testing/staging copy(ies) and production. So, we also need some synchronization between them.

I met some projects where was one master copy of rules in production and everyone should get the copy from there.

The database seed class is much better idea(example). You only need to run

php artisan db:seed AuthSeeder

to have fresh version of auth rules in your database. So, this seed class becomes some kind of Single Source Of Truth. Ok, but there is still some inconvenience with this way:

  • Database seeder should be smart enough to syncronize rules. I saw an example of database seeder in these packages documentation. It only can create roles and permissions, not synchronize.

  • The rules still in database and have to be synced every time. Each requirement change, like 'editors shouldn't edit posts now, only publish them' will consequence changes in authorization database seeder, syncing codebase by git or something else and 'don't forget to run AuthSeeder!'

  • Authorization rules also can be complex. Post can be edited not only by editors and administrators but also by owner of this post. So, instead of simple middleware like:

['middleware' => ['permission:edit posts']]

developers have to use standard Laravel's authorization:

class PostPolicy
{
    public function edit(User $user, Post $post)
    {
         return $user->id == $post->owner_id 
             || $user->hasPermissionTo('edit posts');
    }
}

Maybe the simplest way is not the best?

It only looks simple: just install package, run ready migration and go on. From long-term point of view for 90% projects it's not the best choice.

Let's try to analyze what exactly almost all of these projects wants? Usually it's just a simple role system. One role per user. User can be regular or administrator. Or maybe also an editor, or moderator. And these roles have some additional permissions. Just add a new field to users table! is_admin or role. Then some helper methods to User class:

class User
{
    public function isAdmin(): bool {...}
    public function isEditor(): bool {...}
}

Now, permissions. Laravel provides two main ways to describe permissions: Gates and Policies. I'll use gates(I also used a little trick with prepared closures):

    $isAdmin = function (User $user) {
        return $user->isAdmin();
    }

    $isEditorOrAdmin = function (User $user) {
        return $user->isAdmin() || $user->isEditor();
    }

    Gate::define('foo-permission', $isAdmin);
    Gate::define('bar-permission', $isAdmin);

    Gate::define('editor-permission', $isEditorOrAdmin);

    // Complex permission
    Gate::define('edit-post', function(User $user, Post $post) {
         return $user->id == $post->owner_id 
            || $user->isAdmin();
    });

If project needs multiple roles per user, it also can be implemented by user_roles table and remaking User class helper methods. The content of authorization database seeder for *trust packages and this code-based authorization configuration are almost the same! But now we don't have to store and sync this configuration in different databases.

I don't want to say these packages are useless. They can be useful for projects which needs complicated authorization system, which should be customizable by customer later. Also some projects need dynamic permissions. Example: forum with subforums. Each subforum has own moderators, each moderator has different allowed actions configurable by administrator. This kind of projects really need complex database-based authorization system, with all of their problems. For all other projects it's too heavy solution. Better to use simple code-based authorization.

Each time when developer installing some laravel package, he should think do this project really needs this package? Otherwise it looks like:

Hey, I want to implement $a + $b sum calculation in my project? Is there any laravel-sum package?