CoderFunda
  • Home
  • About us
    • Contact Us
    • Disclaimer
    • Privacy Policy
    • About us
  • Home
  • Php
  • HTML
  • CSS
  • JavaScript
    • JavaScript
    • Jquery
    • JqueryUI
    • Stock
  • SQL
  • Vue.Js
  • Python
  • Wordpress
  • C++
    • C++
    • C
  • Laravel
    • Laravel
      • Overview
      • Namespaces
      • Middleware
      • Routing
      • Configuration
      • Application Structure
      • Installation
    • Overview
  • DBMS
    • DBMS
      • PL/SQL
      • SQLite
      • MongoDB
      • Cassandra
      • MySQL
      • Oracle
      • CouchDB
      • Neo4j
      • DB2
      • Quiz
    • Overview
  • Entertainment
    • TV Series Update
    • Movie Review
    • Movie Review
  • More
    • Vue. Js
    • Php Question
    • Php Interview Question
    • Laravel Interview Question
    • SQL Interview Question
    • IAS Interview Question
    • PCS Interview Question
    • Technology
    • Other

25 August, 2022

Laravel Roles and Permissions: Gates and Policies Explained

 Programing Coderfunda     August 25, 2022     Laravel, Laravel Tutorials, Packages     No comments   

 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:

1use App\Models\User;
2use Illuminate\Support\Facades\Gate;
3 
4class AppServiceProvider extends ServiceProvider
5{
6 public function boot()
7 {
8 // Should return TRUE or FALSE
9 Gate::define('manage_users', function(User $user) {
10 return $user->is_admin == 1;
11 });
12 }
13}

resources/views/navigation.blade.php:

1<ul>
2 <li>
3 <a href="{{ route('projects.index') }}">Projects</a>
4 </li>
5 @can('manage_users')
6 <li>
7 <a href="{{ route('users.index') }}">Users</a>
8 </li>
9 @endcan
10</ul>

routes/web.php:

1Route::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:

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

Option 2. Controller: can() / cannot()

In the first lines of the Controller method, we can see something like this, with methods can() or cannot(), identical to the Blade directives:

1public function store(Request $request)
2{
3 if (!$request->user()->can('create_users'))
4 abort(403);
5 }
6}

The opposite is cannot():

1public function store(Request $request)
2{
3 if ($request->user()->cannot('create_users'))
4 abort(403);
5 }
6}

Or, if you don't have a $request variable, you can use auth() helper:

1public function create()
2{
3 if (!auth()->user()->can('create_users'))
4 abort(403);
5 }
6}

Option 3. Gate::allows() or Gate::denies()

Another way is to use a Gate facade:

1public function store(Request $request)
2{
3 if (!Gate::allows('create_users')) {
4 abort(403);
5 }
6}

Or, the opposite way:

1public function store(Request $request)
2{
3 if (Gate::denies('create_users')) {
4 abort(403);
5 }
6}

Or, a shorter way to abort, with helpers:

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

Option 4. Controller: authorize()

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

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

Option 5. Form Request class:

I've noticed that many developers generate Form Request classes just to define the validation rules, totally ignoring the first method of that class, which is authorize().

You can use it to check the gates as well. This way, you're achieving a separation of concerns, which is a good practice for solid code, so the Controller doesn't take care of the validation, because it's done in its dedicated Form Request class.

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

And then, in the Form Request:

1class StoreProjectRequest extends FormRequest
2{
3 public function authorize()
4 {
5 return Gate::allows('create_users');
6 }
7 
8 public function rules()
9 {
10 return [
11 // ...
12 ];
13 }
14}

Policy: Model-Based Set of Permissions

If your permissions can be assigned to an Eloquent model, in a typical CRUD Controller, you can build a Policy class around them.

If we run this command:

1php artisan make:policy ProductPolicy --model=Product

It will generate the file app/Policies/UserPolicy.php, with the default methods that have a comment to explain their purpose:

1use App\Models\Product;
2use App\Models\User;
3 
4class ProductPolicy
5{
6 use HandlesAuthorization;
7 
8 /**
9 * Determine whether the user can view any models.
10 */
11 public function viewAny(User $user)
12 {
13 //
14 }
15 
16 /**
17 * Determine whether the user can view the model.
18 */
19 public function view(User $user, Product $product)
20 {
21 //
22 }
23 
24 /**
25 * Determine whether the user can create models.
26 */
27 public function create(User $user)
28 {
29 //
30 }
31 
32 /**
33 * Determine whether the user can update the model.
34 */
35 public function update(User $user, Product $product)
36 {
37 //
38 }
39 
40 /**
41 * Determine whether the user can delete the model.
42 */
43 public function delete(User $user, Product $product)
44 {
45 //
46 }
47 
48 /**
49 * Determine whether the user can restore the model.
50 */
51 public function restore(User $user, Product $product)
52 {
53 //
54 }
55 
56 /**
57 * Determine whether the user can permanently delete the model.
58 */
59 public function forceDelete(User $user, Product $product)
60 {
61 //
62 }
63}

In each of those methods, you define the condition for the true/false return. So, if we follow the same examples as Gates before, we can do this:

1class ProductPolicy
2{
3 public function create(User $user)
4 {
5 return $user->is_admin == 1;
6 }

Then, you can check the Policy in a very similar way as Gates:

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

So, you specify the method name and the class name of the Policy.

In other words, Policies are just another way to group the permissions, instead of Gates. If your actions are mostly around CRUDs of Models, then Policies are probably a more convenient and better-structured option than Gates.


Role: Universal Set of Permissions

Let's discuss another confusion: in Laravel docs, you won't find any section about User Roles. The reason is simple: the term "roles" is artificially made up, to group the permission under some kind of name, like "administrator" or "editor".

From the framework point of view, there are no "roles", only gates/policies that you can group by in whatever way you want.

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:

  • Is it one role or multiple roles?
  • Can a user have one role or multiple roles?
  • Who can manage roles in the system?
  • etc.

So, the Role functionality is another layer of your Laravel application. This is where we get to the Laravel packages that may help. But we can also create the roles without any package:

  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

The last bit is the most crucial.

So, instead of:

1class ProductPolicy
2{
3 public function create(User $user)
4 {
5 return $user->is_admin == 1;
6 }

You would do something like:

1class ProductPolicy
2{
3 public function create(User $user)
4 {
5 return $user->role_id == Role::ADMIN;
6 }

Again, here you have a few options to check the roles. In the example above, we assume there's a belongsTo relationship from User to Role, and also there are constants in the Role model like ADMIN = 1, like EDITOR = 2, just to avoid querying the database too much.

But if you prefer to be flexible, you can query the database every time:

1class ProductPolicy
2{
3 public function create(User $user)
4 {
5 return $user->role->name == 'Administrator';
6 }

But remember to eager load "role" relationship, otherwise, you can easily run into an N+1 query problem here.


Making it Flexible: Permissions Saved in DB

In my personal experience, the usual model of building it all together is this:

  • 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.
1$roles = Role::with('permissions')->get();
2$permissionsArray = [];
3foreach ($roles as $role) {
4 foreach ($role->permissions as $permissions) {
5 $permissionsArray[$permissions->title][] = $role->id;
6 }
7}
8 
9// Every permission may have multiple roles assigned
10foreach ($permissionsArray as $title => $roles) {
11 Gate::define($title, function ($user) use ($roles) {
12 // We check if we have the needed roles among current user's roles
13 return count(array_intersect($user->roles->pluck('id')->toArray(), $roles)) > 0;
14 });
15}

In other words, we don't check any access by roles. Role is just an "artificial" layer, a set of permissions that is transformed into Gates during the application lifecycle.

Looks complicated? No worries, this is where we come to packages that can help.


Packages To Manage Roles/Permissions

The most popular packages for this are Spatie Laravel Permission and Bouncer, I have a separate long article about them. The article is very old, but the market leaders are still the same, because of their stability.

What those packages do is help you to abstract the permission management into a human-friendly language, with methods that you can easily remember and use.

Look at this beautiful syntax from Spatie permission:

1$user->givePermissionTo('edit articles');
2$user->assignRole('writer');
3$role->givePermissionTo('edit articles');
4$user->can('edit articles');

Bouncer is maybe a bit less intuitive but still very good:

1Bouncer::allow($user)->to('create', Post::class);
2Bouncer::allow('admin')->to('ban-users');
3Bouncer::assign('admin')->to($user);

You can read more about how to use those packages in the links to their Github, or my article above.

So, these packages are the final "layer" of authentication/authorization that we cover here in this article, I hope now you get the full picture and will be able to pick what strategy to use.


P.S. Wait, What About Guards?

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))

I even submitted a Pull Request to the docs with a warning to avoid this misunderstanding.

In the official documentation, you may find this paragraph:

At its core, Laravel's authentication facilities are made up of "guards" and "providers". Guards define how users are authenticated for each request. For example, Laravel ships with a session guard which maintains state using session storage and cookies.

So, guards are a more global concept than roles. One example of a guard is "session", later in the documentation, you may see a JWT guard example. In other words, a guard is a full authentication mechanism, and for the majority of Laravel projects, you won't ever need to change the guard or even know how they work. Guards are outside of this roles/permissions topic.

  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg
Email ThisBlogThis!Share to XShare to Facebook

Related Posts:

  • Laravel Nova Inline Select avel Nova Inline Select is a package that allows you to change the status of a resource directly from the index or detail views."We have some in… Read More
  • Scan Machine-Readable Documents with Identity Documents for Laravel  Identity Documents for Laravel is a package that allows you to handle documents like passports and other machine-readable documents. We origin… Read More
  • Notion API for LaravelNotion API for Laravel is a package to effortlessly create Notion integrations with Laravel:This package provides a simple and crisp way to … Read More
  • Inertia.js Adapter for Statamic nertia-statamic by Adam Campbell is a Statamic server-side adapter for Intertia.js used to build single-page apps without buildin… Read More
  • Get Current and Historical Currency Exchange Rates in Laravel  Laravel Currency is a package that provides current and historical exchange rates for currency and cryptocurrencies. The package leverages the e… Read More
Newer Post Older Post Home

0 comments:

Post a Comment

Thanks

Meta

Popular Posts

  • Spring boot app (error: method getFirst()) failed to run at local machine, but can run on server
    The Spring boot app can run on the online server. Now, we want to replicate the same app at the local machine but the Spring boot jar file f...
  • Log activity in a Laravel app with Spatie/Laravel-Activitylog
      Requirements This package needs PHP 8.1+ and Laravel 9.0 or higher. The latest version of this package needs PHP 8.2+ and Laravel 8 or hig...
  • Vue3 :style backgroundImage not working with require
    I'm trying to migrate a Vue 2 project to Vue 3. In Vue 2 I used v-bind style as follow: In Vue 3 this doesn't work... I tried a...
  • Laravel auth login with phone or email
          <?php     Laravel auth login with phone or email     <? php     namespace App \ Http \ Controllers \ Auth ;         use ...
  • Enabling authentication in swagger
    I created a asp.net core empty project running on .net6. I am coming across an issue when I am trying to enable authentication in swagger. S...

Categories

  • Ajax (26)
  • Bootstrap (30)
  • DBMS (42)
  • HTML (12)
  • HTML5 (45)
  • JavaScript (10)
  • Jquery (34)
  • Jquery UI (2)
  • JqueryUI (32)
  • Laravel (1017)
  • Laravel Tutorials (23)
  • Laravel-Question (6)
  • Magento (9)
  • Magento 2 (95)
  • MariaDB (1)
  • MySql Tutorial (2)
  • PHP-Interview-Questions (3)
  • Php Question (13)
  • Python (36)
  • RDBMS (13)
  • SQL Tutorial (79)
  • Vue.js Tutorial (68)
  • Wordpress (150)
  • Wordpress Theme (3)
  • codeigniter (108)
  • oops (4)
  • php (853)

Social Media Links

  • Follow on Twitter
  • Like on Facebook
  • Subscribe on Youtube
  • Follow on Instagram

Pages

  • Home
  • Contact Us
  • Privacy Policy
  • About us

Blog Archive

  • September (100)
  • August (50)
  • July (56)
  • June (46)
  • May (59)
  • April (50)
  • March (60)
  • February (42)
  • January (53)
  • December (58)
  • November (61)
  • October (39)
  • September (36)
  • August (36)
  • July (34)
  • June (34)
  • May (36)
  • April (29)
  • March (82)
  • February (1)
  • January (8)
  • December (14)
  • November (41)
  • October (13)
  • September (5)
  • August (48)
  • July (9)
  • June (6)
  • May (119)
  • April (259)
  • March (122)
  • February (368)
  • January (33)
  • October (2)
  • July (11)
  • June (29)
  • May (25)
  • April (168)
  • March (93)
  • February (60)
  • January (28)
  • December (195)
  • November (24)
  • October (40)
  • September (55)
  • August (6)
  • July (48)
  • May (2)
  • January (2)
  • July (6)
  • June (6)
  • February (17)
  • January (69)
  • December (122)
  • November (56)
  • October (92)
  • September (76)
  • August (6)

  • Failed to install 'cordova-plugin-firebase': CordovaError: Uh oh - 9/21/2024
  • pyspark XPath Query Returns Lists Omitting Missing Values Instead of Including None - 9/20/2024
  • SQL REPL from within Python/Sqlalchemy/Psychopg2 - 9/20/2024
  • MySql Explain with Tobias Petry - 9/20/2024
  • How to combine information from different devices into one common abstract virtual disk? [closed] - 9/20/2024

Laravel News

  • Sublime Text Releases Update With Support for Right Sidebar - 5/22/2025
  • Enhance Email Validation with Laravel's Fluent Email Rule Object - 5/18/2025
  • Locale-aware Number Parsing in Laravel 12.15 - 5/21/2025
  • Handle Fluent Values as Arrays with Laravel's array() Method - 5/18/2025
  • Chargebee Starter Kit for Billing in Laravel - 5/20/2025

Copyright © 2025 CoderFunda | Powered by Blogger
Design by Coderfunda | Blogger Theme by Coderfunda | Distributed By Coderfunda