• Reading time ~ 10 min
  • 02.06.2022

In Laravel, roles and permissions have been one of the most confusing topics over the years. Mostly, because there is no documentation about it: the same things "hide" under other terms in the framework, like "gates", "policies", "guards", etc. In this article, I will try to explain them all in "human language".

Gate is the same as Permission

One of the biggest confusions, in my opinion, is the term "gate". I think developers would have avoided a lot of confusion if they were called what they are.

Gates are Permissions, just called by another word.

What are the typical actions we need to perform with permissions?

  • Define the permission, ex. "manage_users"
  • Check the permission on the front-end, ex. show/hide the button
  • Check the permission on the back-end, ex. can/can't update the data

So yeah, replace the word "permission" with "gate", and you understand it all.

A simple Laravel example would be this:

app/Providers/AppServiceProvider.php:

use App\Models\User;
use Illuminate\Support\Facades\Gate;
 
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
    	// Should return TRUE or FALSE
        Gate::define('manage_users', function(User $user) {
            return $user->is_admin == 1;
        });
    }
}

resources/views/navigation.blade.php:

<ul>
    <li>
        <a href="{{ route('projects.index') }}">Projects</a>
    </li>
    @can('manage_users')
    <li>
        <a href="{{ route('users.index') }}">Users</a>
    </li>
    @endcan
</ul>

routes/web.php:

Route::resource('users', UserController::class)->middleware('can:manage_users');

Now, I know that, technically, Gate may mean more than one permission.So, instead of "manage_users", you could define something like "admin_area". But in most examples I've seen, Gate is a synonym for Permission.

Also, in some cases, the permissions are called "abilities", like in the Bouncer package. It also means the same thing - ability/permission for some action.We'll get to the packages, later in this article.


Various Ways to Check Gate Permission

Another source of confusion is how/where to check the Gate. It's so flexible that you may find very different examples. Let's run through them:

Option 1. Routes: middleware('can:xxxxxx')

This is the example from above.Directly on the route/group, you may assign the middleware:

Route::post('users', [UserController::class, 'store'])
    ->middleware('can:create_users');

Option 2.

In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

public function store(Request $request)
{
	if (!$request->user()->can('create_users'))
        abort(403);
    }
}

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles.

public function store(Request $request)
{
	if ($request->user()->cannot('create_users'))
        abort(403);
    }
}

Even shorter option, and my favorite one, is to use authorize() in Controllers.In case of failure, it would return a 403 page, automatically.

public function create()
{
	if (!auth()->user()->can('create_users'))
        abort(403);
    }
}

In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles.

public function store(Request $request)
{
	if (!Gate::allows('create_users')) {
        abort(403);
    }
}

Even shorter option, and my favorite one, is to use authorize() in Controllers.In case of failure, it would return a 403 page, automatically.

public function store(Request $request)
{
    if (Gate::denies('create_users')) {
        abort(403);
    }
}

In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

public function store(Request $request)
{
    abort_if(Gate::denies('create_users'), 403);
}

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles.

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

public function store(Request $request)
{
    $this->authorize('create_users');
}

In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles.

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

public function store(StoreUserRequest $request)
{
	// No check is needed in the Controller method
}

In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

class StoreProjectRequest extends FormRequest
{
    public function authorize()
    {
        return Gate::allows('create_users');
    }
 
    public function rules()
    {
        return [
            // ...
        ];
    }
}

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles.

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

php artisan make:policy ProductPolicy --model=Product

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles.

use App\Models\Product;
use App\Models\User;
 
class ProductPolicy
{
    use HandlesAuthorization;
 
    /**
     * Determine whether the user can view any models.
     */
    public function viewAny(User $user)
    {
        //
    }
 
    /**
     * Determine whether the user can view the model.
     */
    public function view(User $user, Product $product)
    {
        //
    }
 
    /**
     * Determine whether the user can create models.
     */
    public function create(User $user)
    {
        //
    }
 
    /**
     * Determine whether the user can update the model.
     */
    public function update(User $user, Product $product)
    {
        //
    }
 
    /**
     * Determine whether the user can delete the model.
     */
    public function delete(User $user, Product $product)
    {
        //
    }
 
    /**
     * Determine whether the user can restore the model.
     */
    public function restore(User $user, Product $product)
    {
        //
    }
 
    /**
     * Determine whether the user can permanently delete the model.
     */
    public function forceDelete(User $user, Product $product)
    {
        //
    }
}

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

class ProductPolicy
{
    public function create(User $user)
    {
        return $user->is_admin == 1;
    }

In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

public function store(Request $request)
{
    $this->authorize('create', Product::class);
}

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles.

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))


In other words, a role is an entity OUTSIDE of the Laravel framework, so we need to build the role structure ourselves.It may be a part of the overall auth confusion, but it makes perfect sense because we should control how roles are defined:

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

So, guards are a more global concept than roles.

  • Is it one role or multiple roles?
  • Can a user have one role or multiple roles?
  • Who can manage roles in the system?
  • etc.
  1. Create "roles" DB table and Role Eloquent Model
  2. Add a relationship from User to Role: one-to-many or many-to-many
  3. Seed the default Roles and assign them to the existing Users
  4. Assign a default Role at the registration
  5. Change Gates/Policies to check the Role instead

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

So, guards are a more global concept than roles.

class ProductPolicy
{
    public function create(User $user)
    {
        return $user->is_admin == 1;
    }
class ProductPolicy
{
    public function create(User $user)
    {
        return $user->role_id == Role::ADMIN;
    }

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

So, guards are a more global concept than roles.

class ProductPolicy
{
    public function create(User $user)
    {
        return $user->role->name == 'Administrator';
    }

Oh, those. They cause so much confusion over the years.Many developers thought that Guards are Roles, and started creating separate DB tables like "administrators", and then assigning those as Guards.Partly, because in the documentation you may find code snippets like Auth::guard('admin')->attempt($credentials))

So, guards are a more global concept than roles.

  • All permissions and roles are saved in the database, managed with some admin panel;
  • Relationships: roles many-to-many permissions, User belongs to Role (or many-to-many roles);
  • Then, in AppServiceProvider you make a foreach loop from all permissions from DB, and run a Gate::define() statement for each of them, returning true/false based on the role;
  • And finally, you check the permissions with @can('permission_name') and $this->authorize('permission_name'), like in the examples above.
$roles = Role::with('permissions')->get();
$permissionsArray = [];
foreach ($roles as $role) {
    foreach ($role->permissions as $permissions) {
        $permissionsArray[$permissions->title][] = $role->id;
    }
}
 
// Every permission may have multiple roles assigned
foreach ($permissionsArray as $title => $roles) {
    Gate::define($title, function ($user) use ($roles) {
    	// We check if we have the needed roles among current user's roles
        return count(array_intersect($user->roles->pluck('id')->toArray(), $roles)) > 0;
    });
}

Looks complicated?


$user->givePermissionTo('edit articles');
$user->assignRole('writer');
$role->givePermissionTo('edit articles');
$user->can('edit articles');
Bouncer::allow($user)->to('create', Post::class);
Bouncer::allow('admin')->to('ban-users');
Bouncer::assign('admin')->to($user);

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

ABOUT

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

About author CrazyBoy49z
WORK EXPERIENCE
Contact
Ukraine, Lutsk
+380979856297