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

sbkl's avatar
Level 17

Laravel authorisations - Roles and permissions with Policies

Hello,

I'm working on my authorisation system and followed the videos from "what's new in Laravel 5.2".

In the Post Policy update method, I would like to check first if the user has the Permission to edit the forum in general like a site manager would be able to even if he didn't write the post.

Then, I would check if the user is the one who has created the post.

This code below doesn't work but cannot figure out what I'm doing wrong.

I think the issue may come from the hasRole method in my user model on the following line:

return !! $role->intersect($this->roles)->count();

Here is the full code:

Policy:

    public function update(User $user, Post $post)
    {
        if ($user->can('edit_forum', $post)) {

            return true;
        }

        return $user->id === $post->user_id;
    }

Role Model

namespace App;


use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    public function permissions()
    {
        return $this->belongsToMany(Permission::class);
    }

    public function givePermissionTo(Permission $permission)
    {
        return $this->permissions()->save($permission);
    }
}

Permission Model

namespace App;

use Illuminate\Database\Eloquent\Model;

class Permission extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}

User Model

use Illuminate\Foundation\Auth\User as Authenticatable;
use App;

class User extends Authenticatable
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'firstName', 'lastName', 'email', 'photo_path', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

    public function assignRole($role)
    {
        if(is_string($role))
        {
            return $this->roles()->save($role);

        }

        return $this->roles()->save(
            Role::whereName($role)->first()
        );
    }

    public function hasRole($role)
    {
        if(is_string($role))
        {
            return $this->roles->contains('name', $role);

        }
        return !! $role->intersect($this->roles)->count();
    }
}

AuthServiceProvider

namespace App\Providers;

use App;
use App\Permission;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        App\Post::class => App\Policies\PostPolicy::class,
    ];

    /**
     * Register any application authentication / authorization services.
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        $this->registerPolicies($gate);

        foreach ($this->getPermissions() as $permission)
        {
            $gate->define($permission->name, function($user) use ($permission)
            {
                return $user->hasRole($permission->roles);
            });
        }
    }

    protected function getPermissions()
    {
        return Permission::with('roles')->get();
    }
}
0 likes
4 replies
sbkl's avatar
sbkl
OP
Best Answer
Level 17

Ok got it.

The mistake was in the can method check in the policy where I just removed the $post object from the arguments:

    public function update(User $user, Post $post)

    {
        if ($user->can('edit_forum')) {

            return true;
        }

        return $user->id === $post->user_id;
    }
AR's avatar

@sbkl How was your path with this approach for mixing the policy and roles/permissions?

I was looking for a solution to solve the same issue when I have scenarios like:

  • Users need to edit their own post only
  • Admin can edit all posts
  • Admin can limit user's access by taking the 'edit_post' permission from them so after that the user wont be able to edit his own post

The solution should cover roles and permissions so that you can give admin all/most of the permissions and the users might have some. Then the users will be restricted to not edit someone else's post. and that can be achieved by checking if the user's id is equal to the post's user_id in the PostPolicy.

For solving both of the issues we need to combine roles and permissions with the PostPolicy. So in your example the first place that gets the check is the update method in the PostPolicy and within that you check that if the user first has the permission to update the post and then check if the post's user_id is equal to the user's id. Another check can be done before all to say if the user->hasRole('Admin') then return true.

Is that a good solution or you have has difficulties with it?

buildmidwestern's avatar

@ar I actually just thought of a solution, I guess the permissions can be broad representations of what a user can do. IE if a user can edit a post ever, and then the policy dwindles it down to single models. So you authorized based on what you're checking for, if they're ever able to, or if they can do it for that specific model.

And we can do this via a case switch,

    /**
     * Determine whether the user can view the model.
     *
     * @param  \App\User  $user
     * @param  \App\Activity  $targetModel
     * @return mixed
     */
    public function view(User $user, Activity $targetModel)
    {
        $userInOrganization = $user->organizations()->where('id', $targetModel->organization_id)->exists();

        switch ($user->roles()->first()->name) {
            case 'super admin':
                return true;
            case ('organization admin' || 'coach'):
                return $userInOrganization;
            default:
                return false;
        }
    }

Please or to participate in this conversation.