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

Garet's avatar
Level 3

Authorization policies and reducing the repitition

I've been trying to get to grips with Laravel's Authorization Policies, and whilst I understand them and how they work, I can't seem to find a way of reducing the amount of repitition required when setting up and configuring policies.

Let's say I have an area in my app called "Customers" where you can view customers, edit customers, add customers, delete customers, etc. I'm not really interested in creating separate logic for each method - in other words a user should be able to do anything customer related, or not at all.

What I end up with is a customer policy like this:

class UserPolicy
{

public function index(User $user)
{
    return $user->hasPermission('customer');
}

public function edit(User $user, Customer $customer)
{
    return $user->hasPermission('customer');
}

public function update(User $user, Customer $customer)
{
    return $user->hasPermission('customer');
}

// The above is repeated for every method that is available in the customer controller
...
}

Then in my customer controller:

class CustomerController extends Controller
{

public function index()
{
    $this->authorize(__FUNCTION__, Customer::class);
    ...
}

public function edit(Customer $customer) {
    $this->authorize(__FUNCTION__, $customer);
    ...
}

public function update(Customer $customer) {
    $this->authorize(__FUNCTION__, $customer);
    ...
}

// Authorize is repeated in every method
...
}

The above principle is then repeated for every policy/model that I have.

I'm wondering if there is a better way?

I know there is $this->authorizeResource() which can be used in the controller's constructor, but then if I have some custom methods (for example, a method called trash() which allows the user to view soft deleted customers) they are not caught by $this->authorizeResource().

Thanks

0 likes
2 replies
jlrdw's avatar
jlrdw
Best Answer
Level 75

I use laravel Authentication, but for gates and policies, I wrote a helper class to check if a user has or does not have access. There is nothing in the docs that prevents you from writing such a class to better organize this stuff.

Every secure method has to be checked one way or the other.

RBAC became a little clearer to me when I pictured it like this.

Pretend you are in a large industrial complex with many rooms and many doors. Depending on access level, security badges are required.

But the badges are different depending on persons security clearance.

They have a maintenance department accounting department Human Resources, executives (admins), scientist, all different levels of employees.

So a user (person) is walking through and each door either user can or cannot enter.

And me I like looking at each controller method the same way, the logged in user can or cannot do that method.

And of course the other part of RBAC, is to allow a user to see and edit their own data while disallowing other uses.

Even Jeffrey in one of his videos mentioned setting this stuff up can be tricky at first but once learned gets easier.

So I look at it like:

// Bob has roles assigned admin and bookkeeper 
// A method in controller Bob wants to access
// This method (function) requires the role to be bookkeeper
// YOU NEED TO PROGRAM IN   (However you need to)

// Is bookkeeper one of Bobs roles.

if  bookkeeper matches one of Bobs stored roles
    allow access
otherwise
    redirect where ever

And as far as a user seeing and editing their own data, see this link:

https://laracasts.com/discuss/channels/laravel/using-laravel-policy-to-filter-eloquent-query

And here are some other general links:

https://laracasts.com/discuss/channels/eloquent/list-of-unique-users-with-certain-roles-from-many-to-many-relationship

https://laracasts.com/discuss/channels/laravel/multi-auth-on-laravel-using-the-same-login-page

https://laracasts.com/discuss/channels/code-review/multi-auth-with-different-login-forms

https://laracasts.com/discuss/channels/laravel/permissions-and-roles

https://laracasts.com/discuss/channels/laravel/duel-roles

The following

I do not condone, or am I saying to do anything this way, but it works for me.

Instead of the code required for gates and policies, I handle at method level. I ensure user is logged in via the auth group in routing.

I have in a custom class a method:

public static function chkRole($role = null)
    {
        $userrole = Auth::user()->role;
        $checkrole = explode(',', $userrole);
        if (in_array($role, $checkrole)) {
            return true;
        }
        return false;
    }

I store roles in a field in users table like:


role

admin,bkeep,user

bkeep short for bookkeeper.

And for a method

public function someMethod()
    {
        if (!ChkAuth::chkRole('admin')) {
            return redirect('where_ever');  // or whatever you need to do.
        }
        // rest of method

Again

I am in no way saying how to implement your RBAC, rather just showing how it works using this.

Above could have been one line of code, but a redirect isn't allowed in a ternary statement.

Also if a whole controller needs a certain role, just one check in the construct method is needed.

In the case of differentiating an admin vs user to determine what can be viewed in query results, and allowing a user to view or edit their own data, is at query level as in one of the links above.

That's where Auth::user()->id comes in. It is the key to the user only seeing what they are supposed to see in a query.

1 like
Garet's avatar
Level 3

Thanks @jlrdw

Part of my frustration is that policies seemed to have to relate to a model, however there were cases where I wanted to check a user's role without it being model related.

Anyway, my solution was as follows:

In AuthServiceProvider.php I registered a gate called is_admin:

public function boot()
{
    $this->registerPolicies();

    Gate::define('is_admin', function ($user) {
        return ($user->is_admin && $user->active);
    });
}

I then apply this gate via middleware on the routes. This is useful because I can check if a user is admin on, for example, the dashboard which isn't related to a particular model.

Then I create a policy for each model, for example a customer policy with one method:

class CustomerPolicy
{
    use HandlesAuthorization;

    /**
     * Check if the user has permission to access any customer methods
     *
     * @param  User $user
     * @return boolean
     */
    public function customer(User $user)
    {
        return $user->hasPermission('customer');
    }
}

My user model has a method called hasPermission() which checks the user has permission to access a particular area. I then check this in the controllers constructor via middleware:

class CustomerController extends Controller
{
    public function __construct()
    {
        $this->middleware('can:customer,App\Customer');
        return parent::__construct();
    }
}

Equally this could be done in the routes file instead of the controller's constructor.

I then repeat the above for each controller/model. The only exception is my user controller/model where the policy not only checks whether the user is allowed to view and edit other users, but whether they can edit their own user profile.

There is still a bit of repition with this solution but it isn't too bad. I guess I could get rid of the policies and write my own middleware function that accepts a user and a string and is generic to all controllers.

Please or to participate in this conversation.