Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

RomainLanz's avatar

Laravel ACL

Hi everyone,

I'm using the laravel-acl package made by @kodeine.

I've a question about Route Protection (https://github.com/kodeine/laravel-acl/wiki/Protect-Routes).

I start with this role/permission:

Permission::create([
    'name' => 'internships.all',
    'slug' => [
        'create' => true,
        'view'   => true,
        'update' => true,
        'delete' => true,
    ],
    'description' => 'Full access to internships resource'
]);

$role = Role::create([
    'name' => 'Admin',
    'slug' => 'admin',
    'description' => 'Administrator of the website'
]);
$role->assignPermission('internships.all');

My question is "How can I protect my resource's route?".

If we look at my route file:

Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'acl'], 'can' => 'view.dashboard.admin'], function()
{

    ...

    /**
     * Internship resource.
     */
    get('internships', ['as' => 'admin.internships.index', 'can' => 'view.internships', 'uses' => 'Admin\InternshipsController@index']);
    post('internships', ['as' => 'admin.internships.store', 'can' => 'store.internships', 'uses' => 'Admin\InternshipsController@store']);
    get('internships/create', ['as' => 'admin.internships.create', 'can' => 'create.internships', 'uses' => 'Admin\InternshipsController@create']);
    get('internships/{internships}', ['as' => 'admin.internships.show', 'can' => 'show.internships', 'uses' => 'Admin\InternshipsController@show']);
    put('internships/{internships}', ['as' => 'admin.internships.update', 'can' => 'update.internships', 'uses' => 'Admin\InternshipsController@update']);
    get('internships/{internships}/edit', ['as' => 'admin.internships.edit', 'can' => 'edit.internships', 'uses' => 'Admin\InternshipsController@edit']);

    ...
    
}

I want to check if the user can CRUD an internhips BUT with this package (and according to my permission), I need to write something like:

get('internships', ['as' => 'admin.internships.index', 'can' => 'view.internships.all', 'uses' => 'Admin\InternshipsController@index']);

So the permission are completely useless? Because they need to be unique, and we need to write their names on the routes file.

Do I miss understand how to do that or it's a misconception of the package?

0 likes
37 replies
kodeine's avatar

@RomainLanz Since you have InternshipsController methods named as index, create, store etc. you can just protect your methods using

Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'acl'], 'protect_alias' => 'internships'], function() {}

Also you would need to change your permission alias name from internships.all to internships.

Since you are using CRUD you don't have to define can in your get/post/put routes separately. protect_alias will take care of your crud methods itself. If you are not using protect_alias then you can define can param to each of your route.

// If not using protect_alias, use Can param.
get('internships', ['as' => 'admin.internships.index', 'can' => 'view.internships', 'uses' => 'Admin\InternshipsController@index']);

or 

// If using protect_alias, you dont need Can param.
get('internships', ['as' => 'admin.internships.index', 'uses' => 'Admin\InternshipsController@index']);
RomainLanz's avatar

@kodeine

Ok, but now let's assume that I've another permission. Something like:

Permission::create([
    'name' => 'internships',
    'slug' => [
        'create' => false,
        'view'   => true,
        'update' => true,
        'delete' => true,
    ],
    'description' => 'Blah blah blah'
]);

$role = Role::create([
    'name' => 'Teacher',
    'slug' => 'teacher',
    'description' => 'Teacher of the website'
]);
$role->assignPermission('internships');

I can't create this permission because it has the same name as this other. How can I handle that?

kodeine's avatar

@RomainLanz, just read my code again, you can use internships.all in protect_alias, it wouldn't be a problem :))

RomainLanz's avatar

@kodeine Yes yes, I understand.

I want to have permissions about an entity on my database, like Internship for this example. I've different permission on this entity. What I do before (on Laravel 4) is:

if ($user->hasRole('student')) {
    $authority->allow('read', Internship::class);
}
if ($user->hasRole('teacher')) {
    $authority->allow('manage', Internship::class);
}
if ($user->hasRole('partner')) {
    $authority->allow('create', Internship::class);
}
if ($user->hasRole('admin')) {
    $authority->allow('manage', 'all');
}

I can set right on a specific model.

I don't understand how I can replicate that with your package because permission is unique for an entity.

kodeine's avatar

@RomainLanz, if i have understood your question well, you dont have to protect your controller methods anywhere, you just do it on routes.

RomainLanz's avatar

You don't understand what I mean @kodeine, but it's probably my fault. ^^

Before (on Laravel 4), I could create permissions for a model by roles.
In my config file I had:

$user = Auth::guest() ? new User : $authority->getCurrentUser();

if ($user->hasRole('student')) {
    $authority->allow('read', Internship::class);
    $authority->allow('create', Proposal::class);
    $authority->allow(['read', 'edit'], User::class);
}
if ($user->hasRole('teacher')) {
    $authority->allow('manage', Internship::class);
    $authority->allow(['read', 'update', 'destroy'], Proposal::class);
    $authority->allow('manage', User::class);
}
if ($user->hasRole('partner')) {
    $authority->allow('create', Internship::class);
    $authority->allow('read', User::class);
}
if ($user->hasRole('admin')) {
    $authority->allow('manage', 'all');
}

And with that I could do something like:

@if (Authority::can('update', $internship))
    {{ link_to_route('internships.edit', 'Editer', $internship->id, ['class' => 'btn btn-xs btn-default pull-right']) }}
@endif

Now with your package, I can't set different permissions for a model by roles or I don't understand how to do that.
What I finally want, it's writing something like:

get('internships', ['as' => 'admin.internships.index', 'can' => 'view.internships', 'uses' => 'Admin\InternshipsController@index']);

And all roles have a different value for view.internships permission.

kodeine's avatar

@RomainLanz,

Based on the following permission.

Permission::create([
    'name' => 'internships.all',
    'slug' => [
        'create' => true,
        'view'   => true,
        'update' => true,
        'delete' => true,
    ],
    'description' => 'Full access to internships resource'
]);

internships.all can create, view, update, delete and their permissions are set to true.

So anywhere you can write $user->can('create.internships.all, view.internships.all'); // will result to true. since you used a comma, it will use AND operator and will make sure that both permissions are allowed. If you pass $user->can('create.internships.all|view.internships.all'); separated by pipe, it will use OR operator.

RomainLanz's avatar

@kodeine, I agree with you, but now imagine that I want to change the permission create.internships.all to false only for my Student role. I can't do that with your package right?

kodeine's avatar

@RomainLanz why not create Role student and assign permission internships.student and set create to false there. Then you can check permission $user->can('create.internships.student');

Similarly if you have Role Teacher, assign permission internships.teacher, set different values and call $user->can('create.internships.teacher');

kodeine's avatar

Ahh, i think i know what you mean now, i will try to update the package to support Entity validations on roles.

RomainLanz's avatar

@kodeine So every time I create a new permission I need to specify it on my routes file or view/controller?

If I've 10 roles with 10 entities, I need to set 100 permissions to handle all? And I need to specify 100 times on my routes file?

With your example, to check if my Student, Teacher, Partner or Admin can create an internships I need to write my route like that:

get('internships/create', ['as' => 'admin.internships.create', 'can' => 'create.internships.student|create.internships.teacher|create.internships.partner|create.internships.admin', 'uses' => 'Admin\InternshipsController@create']);

Instead of just writing something like:

get('internships/create', ['as' => 'admin.internships.create', 'can' => 'create.internships.', 'uses' => 'Admin\InternshipsController@create']);

So permission are useless, it's completely attached to a role and not to an entity.

EDIT: Haha, perfect, let me know when you update your package to test. :D

RomainLanz's avatar

@kodeine, if you want to keep simplicity you can just set permission like this.

// This is probably useless
// Permission::create([
//     'model' => 'internships'
// ]);

$role = Role::create([
    'name' => 'Admin',
    'slug' => 'admin',
    'description' => 'Administrator of the website'
]);
$role->assignPermission('internships', ['create', 'read', 'update', 'delete']); // Create appropriate record on the database

$role = Role::create([
    'name' => 'Student',
    'slug' => 'student',
    'description' => 'Student [...]'
]);
$role->assignPermission('internships', 'read');
kodeine's avatar

@RomainLanz, yes that would probably be the best scenario.

So user can have multiple roles? Admin and Student both? having multiple roles will mess up the permissions isnt it?

kodeine's avatar

@RomainLanz, Following scenario would work if user has a single role only.

what if when we $role->assignPermission('internships', 'read'); we store them like permission.role so internships.admin and internships.student

`permissions` (`id`, `name`, `slug`, `description`, `created_at`, `updated_at`) VALUES
(121,   'internships.admin',    '{\"create\":true,\"read\":true,\"update\":true,\"delete\":true}',  'admin',    '0000-00-00 00:00:00',  '0000-00-00 00:00:00'),
(123,   'internships.student',  '{\"read\":true}',  'student',  '0000-00-00 00:00:00',  '0000-00-00 00:00:00');

And when we need to use Can method we can find the permissions like.

$user->can('view.internships');   // user has a student role, so we check permissions from internships.student.
RomainLanz's avatar

@kodeine, what I propose for a second version of your package.

// We create a role
$role = Role::create([
    'name' => 'Admin',
    'slug' => 'admin',
    'description' => 'Administrator of the website'
]);

// And assign permission
$role->assignPermission('internships', [
    'allow' => ['create', 'read', 'update', 'delete']
]);
// role_permissions
|   id   |   role_id   |   rights   |
+ ------ + ----------- + ---------- +
|    1   |      1      |   {JSON}   |

The JSON look like

{
    'internships' => [
        'allow' => ['create', 'read', 'delete'],
        'deny'  => ['update']
    ]
}

And to keep good performance we have a table named user_permission_cache with:

|   user_id   |   rights   |
+ ----------- + ---------- +
|      1      |   {JSON}   |

Now you need a single query to get all rights for a specific user. When you update the right of a role or a user this table will be regenerated. If a user has many roles you can use NFS right order. The more permissive is used.

if (false || true) {
    // True is used :)
}
1 like
RomainLanz's avatar

You can also store rights columns from role_permissions directly on the roles table.

1 like
RomainLanz's avatar

Oh, and for specific user permissions you can create a special role and this one is more important than others, so you can override all permissions.

// roles
|   id   |        name        |   specific   |
+ ------ + ------------------ + ------------ +
|    1   |   '1.romainlanz'   |     true     |

// Name's column is : user.id + user.username ; you can define this on a configuration file
kodeine's avatar

@RomainLanz, it will take me few days at least, i have some projects due this week. Feel free to send PR meanwhile. Else i will begin with it in few days.

kodeine's avatar
kodeine
Best Answer
Level 3

@RomainLanz, i have used a little different approach, permission inheriting. So when you create new permission, you can set inheritance of a different permission. Let the example code speak.

First we create Roles.

        $roleTeacher = Role::create([
            'name'        => 'Teacher',
            'slug'        => 'teacher',
            'description' => 'Teacher [...]'
        ]);

        $roleStudent = Role::create([
            'name'        => 'Student',
            'slug'        => 'student',
            'description' => 'Student [...]'
        ]);

Now lets create Permissions for Teacher and Student.

        $permissionInternship = Permission::create([
            'name'        => 'internships',
            'slug'        => [ // an array of permissions.
                'create' => true,
                'view'   => true,
                'update' => true,
                'delete' => true,
            ],
            'description' => 'manage internships'
        ]);

        $permissionStudent = Permission::create([
            'name'        => 'internships.student',
            'slug'        => [ // an array of permissions only for student
                'create' => false,
            ],
            // we use permission inheriting.
            'inherit_id' => $permissionInternship->getKey(),
            'description' => 'student internship permissions'
        ]);

Note: inherit_id in internships.student. since internships.student inherit permissions from internships we can can forget about internships.student because now we recognize it as internships. so getPermissions will return array('internships' => [...permissions merged with internships.student...])

Lets assign those permissions to newly created roles.

$roleTeacher->assignPermission('internships');  // or assignPermission($permissionInternship->id)
$roleStudent->assignPermission('internships.student');

And then we can assign those roles to our user.

$user->assignRole($roleTeacher);
$user->assignRole($roleStudent);
//$user->revokeRole('teacher');

Finally, lets do our tests.

// user has teacher and student role
dump($user->can('create.internships')); // results true

// user has teacher role
dump($user->can('create.internships')); // results true

// user has student role
dump($user->can('create.internships')); // results false

dump($user->getPermissions());

Let me know what you think of this. I am doing final touches and will upload to github shortly. Should i be tagging it as 0.1.1 ?

RomainLanz's avatar

I'm not a fan of this solution, but it seems to work as I wanted and it's maybe easier to code.

Can I test this version?

RomainLanz's avatar

@kodeine Seems to not correctly inherit.

My test :

$p = Permission::create([
    'name' => 'internships',
    'slug' => [
        'create' => true,
        'view'   => true,
        'update' => true,
        'delete' => true,
    ],
    'description' => 'Full access to internships resource',
]);

Permission::create([
    'name' => 'internships.student',
    'slug' => [
        'create' => false,
        'update' => false,
        'delete' => false,
    ],
    'description' => "Internship's permissions for student",
    'inherit_id' => $p->getKey(),
]);

Permission::create([
    'name' => 'internships.teacher',
    'slug' => [
        'create' => false,
        'update' => false,
    ],
    'description' => "Internship's permissions for teacher",
    'inherit_id' => $p->getKey(),
]);

Then I assign permission :

$role = Role::create([
    'name' => 'Teacher',
    'slug' => 'teacher',
    'description' => 'Teacher of CPNV-ES School'
]);
$role->assignPermission(['internships.teacher']);

When I do 'can' => 'view.internships' for a teacher I got an exception because I don't have access to this resource. So I need to write all permissions for all inherited permissions?

EDIT : I've tested with that:

Permission::create([
    'name' => 'internships.teacher',
    'slug' => [
        'create' => false,
        'update' => false,
        'view' => true,
    ],
    'description' => "Internship's permissions for teacher",
    'inherit_id' => $p->getKey(),
]);

And it still failed with the route

get('internships', ['as' => 'admin.internships.index', 'can' => 'view.internships', 'uses' => 'Admin\InternshipsController@index']);
Next

Please or to participate in this conversation.