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

mallorca's avatar

Where to place the logic for permissions

Hey artisans!

I started off by creating a very simple permissions solution. I added a 'role' column in the users table and depending on what integer it was, the user would have a defined role. Then in the user model, I defined something like this:

    public function hasRoleOfAdmin()
    {
        return ($this->role == 3);
    }

    public function hasRoleOfEditor()
    {
        return ($this->role == 2);
    }

Then in some controllers I do something as simple as:

        if (Auth::check() && Auth::user()->hasRoleOfAdmin())

I also made some simple middlewares that looks like this:

    public function handle($request, Closure $next)
    {
        if(auth()->check() && auth()->user()->hasRoleOfAdmin()) 

    {    
        return $next($request);
    }

        return redirect('/');
    }

I decided to make it more flexible so I followed Jeff's series on ACL in Laravel: Part 3: https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/15 and created the whole solution with roles, permissions tables and permission_role and role_user pivot tables.

Now I'm a bit confused. Where would be a good place to place the business logic that replaces my current solution? If anyone that has followed the series could guide me in the right direction and give some tips and examples so that I learn, it would mean a whole lot. Thanks folks!

0 likes
19 replies
jlrdw's avatar

Well look at Jefferies example of authentication kind of use that as a guide but basically a combination of controllers and models. Of course you have middleware for routes. But definitely dig into the docs again and watch a couple of videos on the subject.

1 like
cimrie's avatar

@bobeta

If you are simply checking if someone is logged in as well as having a particular role, I would suggest adding a helper method to your 'Controller' class (the one all the controllers extend from) so that you can throw a permission denied error if they don't have the role.

I would also add a helper class to your User model so that you don't have methods for each type, but just check if the user has a role name. e.g.

class User extends Model {
    ...
    public function hasRole($role_name){
        return ( $this->role->name == $role_name ); //for instance, for a role of 'admin', it will return true for $user->hasRole('admin');
    }

    public function role(){
        return $this->belongsTo(Role::class);
    }
    ...
}
//this is in the Controller class
public function mustHaveRole($role)     //or a name you prefer to use
{  
    $user_has_permission = ( Auth::check() && Auth::user()->hasRole($role) );
    if ( ! $user_has_permission ){
        abort(401, 'You do not have permission to perform this action');
    }
}

Then to use it in any controller that extends from the base 'Controller' class:

class ExampleController extends Controller {
    
    public function index(){
        $this->mustHaveRole('admin'); //this now guards you against anyone that is not logged in or admin, as it will fail internally if they don't have access

        ... //the rest of your logic as normal
    }
}
3 likes
Erik's avatar

For middleware you could do the following

public function handle($request, Closure $next, $role)
{
    if (! $request->user()->hasRole($role)) {
        // Redirect...
    }

    return $next($request);
}

Documentation of this found here.

mallorca's avatar

@Erik Thanks! Could I specify if the user has permission instead of role in the middleware, would it work as intended?

mallorca's avatar

@CImrie Thanks for your time, thats a great solution! How would you suggest I make the middleware?

Erik's example from the docs seems pretty good, but I'm thinking if it maybe would be better to check if the necessary permissions exists in the middleware, instead of checking if the user has the role?

I could then have several roles that have the permissions and can pass to the next request, instead of specifying each role that can access it. What do you think?

1 like
bashy's avatar

Have you tried the Laravel $this->authorize('some-permission'); yet? It's really nice since ACL is now in the core.

mallorca's avatar

@bashy Haven't tried that, looks promising. Do you happen to know how I would implement this in a middleware in a good way?

bashy's avatar

I put mine in each method on my controllers

mallorca's avatar

@bashy That's interesting, so you don't use middleware at all? I'm thinking if using something like $this->authorize('some-permission'); in the controller should to be in contrast with middleware of if it would be a good idea to have synergy between them?

bashy's avatar

Basically I have permissions for all actions file-index - file-show file-edit file-store file-update file-destroy etc. They're attached to a role and a role is attached to a user. I don't need a middleware to show what permissions a user should have since it's all stored in the database. That's what I did for a secure portal system, worked great but that's my take on it.

Have a look at: http://laravel.com/docs/5.1/authorization#controller-authorization

1 like
cimrie's avatar

Hi @Augustus

I myself try to steer away from using Middleware for this, simply because it's likely that you're not using the same 'permission checks' combination more than once or twice at most. If you are using them a lot though and do want to go down the Middleware route, perhaps consider Middleware parameters.

...goes to check docs on middleware parameters...

And what do you know, Taylor has this exact example in the docs! - http://laravel.com/docs/5.1/middleware#middleware-parameters

If you want to check for multiple permissions instead, maybe something like this would be useful:

class PermissionMiddleware
{

    public function handle($request, Closure $next)
    {
        $permissions = array_slice(func_get_args(), 2); 
        //this gets the arguments given to the function except the first two (since that is $request and $next).

        if ( count($permissions) == 0){ //as you aren't requiring any permissions to be on the user
            return $next($request);
        }

        $checks = [];
        foreach ($permissions as $permission){
            $checks[] = Auth::user()->hasPermission($permission);
        }
              
        $passes = array_filter($checks);
        if( count($passes) == count($checks)){ 
        //pretty sure this bit ^^ could be improved but I don't have the energy to think of the most optimal way
            return $next($request);
        } else {
            abort(401, 'You do not have all the necessary permissions to perform this action'); //similar to previous example
        }
    }
}

Then in your routes.php file if you wanted to guard a route with certain permission restrictions:

//routes.php

Route::group(['middleware' => 'permission:article.create,article.edit,article.destroy'], function(){ //this would require 'article.create', 'article.edit' and 'article.destroy' permissions on the user (all of them, it will not pass on just one match)
    //define guarded routes here
});

Of course this assumes you set up Permissions for a User rather than (or in combination with) Roles, and set up the necessary $user->hasPermission($permissionName) helper on the User model.

Hope it helps (again) :)

mallorca's avatar

@CImrie Amazing, thanks! That looks just what I'm looking for. Would it work if I did that and the user has the permission through the role? I have a role_user pivot table and permission_role, just like the ACL permission and roles video example.

So if I add a middleware with article.create and if one of the roles that the user has includes that permission, would they be able to access the route?

cimrie's avatar
cimrie
Best Answer
Level 4

@Augustus

Yes, assuming that hasPermission($permission) exists as a method on your User model you should be good to go.

//User model
public function hasPermission($permission){
    $role = $this->role;
    $role->hasPermission($permission); //I would choose to delegate this task to the same method name on Role model. Otherwise your User model 'knows too much' about the Role model. Personal taste though :)
}

//Role model
public function hasPermission($requiredPermission){
    $permissions = $this->permissions;
    foreach($permissions as $permission){
        if($requiredPermission == $permission){
            return true; //returns true early if the permission is found (as it doesnt need to continue)
        }
    }
    //if none are found then it will eventually reach this
    return false;
}
1 like
mallorca's avatar

@CImrie Amazing. Thanks once again! I changed so that your reply is the answer to the question.

I'm just wondering one last thing regarding this. When storing a user, what would be a good way to do so that I can choose a role for the user so that it gets stored in the pivot table? I'm a bit unsure how to accomplish that.

It was easier earlier since it was just a column in the users table, I hardcoded it like this in the store method of the controller:

$user = new User($request->all());
$user->role = 2;
$user->save();

Then in the edit form for the user, I added possibility to choose a different role. How would you solve this?

1 like
cimrie's avatar

@Augustus ,

If you know the id of the role you should be able to do something like:

$user = new User($request->all());
$user->role()->associate(2);
$user->save();

If you prefer to pass in a 'name' (assuming name is unique) for the role, something like

$user = new User($request->all());
$user->setRole('admin');
$user->save();

//User model class
public function setRole($roleName){
    $role = Role::where('name', $roleName)->first();
    $this->role()->associate($role); //should be able to pass in the role object and it will work the same
}   

Again, code above is untested but it is the general idea. The docs around this idea are at: http://laravel.com/docs/5.1/eloquent-relationships#inserting-related-models

1 like
mallorca's avatar

@CImrie Thanks! I figured it was someting like that. Associate() gave me the following error message:

Call to undefined method Illuminate\Database\Query\Builder::associate()

So I tried with attach() instead and it seems to pass. However I get the following error:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null (SQL: insert into `role_user` (`role_id`, `user_id`) values (3, ))

I guess I need to pass the id of the user as the second parameter. However, since the user hasn't been created yet, how can I access the id of it? Maybe I must attach the role after saving the user?

cimrie's avatar

@Augustus

The reason you can't use associate and can use attach then is because you have set it up as a many to many relationship rather than a one to many. I had made the assumption that a User could only have one role in my examples.

For many to many you have to create the user first, then do the attaching. No way around it I'm afraid as if you think about it, it makes sense that a pivot table needs to know both the ID of the role AND the user to make the connection. Those don't exist until both models exist in the database.

Edit:

So to do that, you should be able to do

$user = new User($request->all());
$user->save();
$user->roles()->attach(2);
1 like
mallorca's avatar

@CImrie Amazing.. You saved my day, thanks a lot for all the help! It means a lot :)

1 like

Please or to participate in this conversation.