88
Php

Laravel – 5.0 ACL Using Middleware

Well, actually building an ACL (Access Control Layer) in Laravel is really very easy than using a third party package and I always prefer my own implementation. In Laravel - 4x we’ve used Route Filter mechanism to build an ACL but in Laravel - 5.0 now we have Middleware instead of Filter and it’s much better, IMO.

The idea behind the ACL is that, we want to protect our routes using user roles and permissions and in this case the underlying mechanism is quite same in both versions but only difference is that, in Laravel - 5.0 the Middleware is the layer (for filtering) between our routes and the application, while in Laravel - 4x the Route Filter was used for filtering the requests before the user gets into the application. In this article, I’ll show how it’s easily possible to implement an ACL from the scratch using Middleware.

It’s possible to implement ACL in different ways but this is what I’ve used in Laravel - 4x and also in Laravel - 5.0 and this could be enhanced or improved but approach may varies but the idea is same, filtering user requests before entering into the application layer.
To implement this, we need 4 tables:
  • 1. users
  • 2. roles
  • 3. permissions
  • 4. permission_role (Pivot Table)
Database migration for users table:

Database migration for roles table:

Database migration for permissions table:

Database migration for permission_role table:

These tables are required to build the ACL fields could be changed (add/remove) but to build the relationship between tables we need foreign keys and we can’t remove those fields such as role_id in users table and the pivot table is also necessary as it is.

Now, we need to create the middleware class to check the user permissions and we can create it using php artisan make:middleware CheckPermission from command line/terminal. This will create a skeleton of a middleware class in app/Http/Middleware directory as CheckPermission.php and now we need to edit that class as given below:

Now, we need to create other classes (Eloquent Model) in app/DB directory. Here, in Laravel - 5.0 the models directory is not available and by default the app directory contains the Eloquent model classes such as User but I’ve created the DB directory to house all of my Eloquent/Fluent classes but it’s not mandatory. Anyways, let’s create those classes (User, Role and Permission) now in app/DB or just in app (The full path of User class must be given in the config/Auth.php file).

The traits are used to separate the code in User class to keep the code clean and easily maintainable, (app/DB/User/Traits/UserACL.php trait):

The app/DB/User/Traits/UserRelationShips.php trait (for relationship methods):

The Role class (app/DB/Role.php):

The Permission class (app/DB/Permission.php):

Now, before we can use our Middleware in any route declaration, we need to add it it in the app/Http/Kernel.php file and by default, there are already other middlewares added in that file by Laravel in the $routeMiddleware array and it looks like this:

We’ll just add our middleware at the end of this array like this:

The acl is the alias which we’ll use in our routes when declaring the routes for limited access, for example take a look at this app/Http/routes.php file:

In this file, all of our routes are protected and requires the user to stay logged in (auth is used to check whether the user is logged in or not, available by default in Laravel) and the acl will check if the user has a given permission or not, for example, the dashboard url/route requires the permission manage_own_dashboard because it has 'permission' => 'manage_own_dashboard' and in our middleware we’ll check if the route has the key permission in it’s action and if the value of permission key (which is a permission) is available in the currently logged in users role permissions list then we’ll allow the user to access the application, otherwise we’ll disallow the user access.

On the time, when this article is being written, the Laravel-5.0 framework isn’t released and after the final release, things may change so please make sure you check the updates on Laravel website and hoping, in the first week of January’15, Laravel-5.0 will be released.
How it works?

Actually, this is just a demonstration of only the ACL but I didn’t provide any code which provides an user interface that let’s the admin a way to create user roles by attaching the permissions but only the core idea to implement the ACL in the application.

So, a brief idea is that, each user will be assigned a role when user account is created from the front-end or by the admin (a default role could be set from the back-end) and the roles will be created by admin (super user) from the back-end and permissions will be attached to each roles. So, a role for example Moderator which could have a permission as Suspend User (permission title) and suspend_user (permission_slug) so we can attach suspend_user in a Route using something like this:

So, this route requires the permission suspend_user and in the acl middleware we can check the user’s permission and can protect the route if the user doesn’t has that permission. That’s it.

In this article, I tried discussed how to organize the tables and classes and how to filter the requests using middleware and what classes could be required but not full implementation of a fully functional system, it just gives an abstract idea to create an ACL functionality using Laravel - 5 Middleware from the scratch (without using any third party package).

Since the Laravel-5.0 is still under development, so I didn’t provide the fully functional code here and once the framework is released, then I’ll write another post, maybe in two parts as a series with fully functional code for building an ACL from the scratch, but for now, that’s all. Thank you.

Update:

In my code example, I’ve used menuItem as a key in route declaration. This key is used to build a dynamic menu and to build the menu I’ve created a class/library which is located at app/Libs/Navigation folder of my project. The menu builder class is given below:

Further, to invoke the menu builder class I’ve created a helper function in a helper file which is app/Helpers/fucntions.php and the function is given below:

Finally, I’ve called the function from my view where I wanted to show the menu and the view is a partial of my admin layout which contains following code:

Stay in touch, full (improved code) will be uploaded on github later.


Update: A follow-up of this article for Laravel-5.1.x posted here.

  • Sysaxiom

    This was really a great post. Thanks πŸ™‚

    • Glad to know that and you are welcome πŸ™‚

  • Danil Chekalin

    I will wait second part. Thx =)

    • Sure, once the framework gets released then I’ll update, stay in touch πŸ™‚

      • Danil Chekalin

        But i don’t understand how the middleware is known about of user
        $request->user()

        • The logged in user (Authenticated) is accessible using the user() method of Illuminate/Http/Request object.

    • @dakiesse:disqus, It just works without any changes πŸ™‚

  • Dave Ganley

    Very nice post. Will have a play when I have some free time

    • @daveganley:disqus, Glad you like it, all the best πŸ™‚

  • Dinsdale

    Thanks a lot for this post, it’s really helpful. Now that L5 is out, any chance for the second part of the series? πŸ™‚

    • Sure, just need a gap from my current job, hope soon I’ll post another article with working code, stay tuned πŸ™‚

    • @timmy_9:disqus, It works without any changes.

  • The Brainβ„’

    Thank you. So implementing your menus like this (menuItem => [icon,title]) saves you from doing stuff like: if currentUser->can(”) then Menu 1 else Menu 2. Pretty smart. πŸ˜€

    • Thanks @jartaud:disqus

      • Thank you @sheikh_heera:disqus , this is really very informative for noob like me, I just want to know, is there any update that to be amended on this code after L5 release.? is this code working after update? This middleware is really confusing me. I want to create admin user and route it to admin panel.

        • @kbhatt2006:disqus Yes, I’ll try very soon to update it, just too busy, This may work but not tested yet πŸ™‚

          • I would like to see a new post with updates about it, thanks!

          • @lenninpadilla:disqus, It works with L-5 without any changes. Let me know if you have any difficulties.

    • Mariah

      Could you pass on any links or example code of how to implement the menu based on the values you are setting in the router? I am not sure how to make that leap. Thanks!

      • @mariahrapier:disqus, Check the updated article, forgot to mention/add it, thanks πŸ™‚

  • Loren Nebiaj

    Thank you πŸ™‚

  • Thanks for this article. This is one approach produces solution with tools comes out of box. However, I really could not understand when and where defined these namespaces AppDBUserTraitsUserAccessors;
    AppDBUserTraitsUserQueryScopes;
    Also, you should share downloadable code for Laravel`s newbies like me πŸ™‚

    • Following are separate files (traits) stored in app/DB/User/Traits folder, I’ve customized the structure to separate the Eloquent Models.

      AppDBUserTraitsUserAccessors;
      AppDBUserTraitsUserQueryScopes;

      • Cernisevski, a Russian writer, says that if you hang a gun in the wall, the gun should burst at end of the game. Due to this, I wondered why they are for…

  • Michael Fairchild

    Thank you fore the article it really helps. I have a question though, I created the DB directory and a User directory inside the DB directoy like you have specified in the article. However, I now get an error “Class ‘AppDBUser’ not found”, I updated config/auth.php to be “‘model’ => ‘AppDBUser’,” and ran composer dump-autoload.

    Have any ideas what might be causing this issue still?

    • Welcome! If you followed exactly then you should use app/DB/Uses/User.php and namespace should be AppDBUser;, hope it helps πŸ™‚

      • Amrender

        Hi Sheikh,
        Can you please let me know why I am getting the same error. I have kept User model under app/ but created DB folder and kept all traits stuff there.

        Thanks in advance.

        • Probably namespace should be changed but not sure because I don’t know what the error is.

  • Buna Rath

    When you put article about acl laravel 5?

    • @bunarath:disqus , Working on it, need more time, too busy with current client’s project. Sorry for the delay but keep in touch, thanks πŸ™‚

      • @bunarath:disqus, Just wanted to notify you that, it works in L-5 without any changes. Let me know if you have any difficulties.

  • The Brainβ„’

    Not working with resource controller:

    Route::group([‘prefix’ => ‘admin’, ‘namespace’ => ‘Admin’, ‘middleware’ => [‘auth’,’acl’]], function(){

    resource(‘countries’, ‘CountriesController’,[‘permission’=>’manage_dashboard’]);

    });

    The problem here is: when the request object get injected into MiddlewareCheckPermission.handle(), the permission key is not present.

    • The Brainβ„’

      Adding the permission key to the group array works great.

  • Brandstone

    Thanks for this guide. It was a really good starting point. I ended up editing a few things to use route names instead of manually assigning permissions to a route. This cleaned up my routes file significantly. I also added a polymorphic relationship from permissions to roles and users, and removed the permissions slug from roles. This allowed me to move everything out of the traits file without crowding my User model. I love how cleanly complex tasks can be accomplished in Laravel.

    • Great! Yes Laravel is awesome πŸ™‚

    • Dan

      Do you have an example of your amends anywhere? Would be great to see what you did with this. Thanks

  • ccostel

    Thanks! I am studying Laravel 5 and your article was an eye opener for the implementation of roles on my test project. Keep up the good work.

    • You are welcome! Glad it was helpful, all the best πŸ™‚

  • Jabed Bangali

    Great implementation of ACL . We can use it after little bit Refactoring like as Sentry. Great job Thank’s.

    • @jabedbangali:disqus, Glad to know you. You are most welcome πŸ™‚

  • Thank , You save my day

    • Glad to know that. You are most welcome πŸ™‚

  • blackburn1911

    Very nice. But I have a problem.

    Trying to get property of non-object on
    $perms = $this->role->permissions->fetch(‘slug’);

  • Do you think you will have a full account example soon?

    • @disqus_6RaHxeL840:disqus, Not very soon because I’m too busy with projects of clients but I’ll write another post with new working code example for multiple role based ACl. Cheesr πŸ™‚

  • La-bas

    If I’m using the repository pattern, do you think it would make sense to put the ACL methods (from UserACL trait) in the User Repository class instead of the User eloquent model?

    • @disqus_5dkREAhoHi:disqus ,The trait is used only to keep the different types of methods in defferent places to better oranize and regarding your question, no because in a trait I’ve used $this which reffers to the User.php and by moving it into the repository, you’ll break the code.

      • La-bas

        Yes, I realize that I would have to change the code. I just believed that having authentication logic in the model was a bad thing.

  • laurens

    I’m getting this: Class ‘AppDBUser.php’ not found

    I changed in auth.php this ”model’ => ‘AppDBUser’,'”
    The file App/DB/User.php exists.

    • laurens

      in EloquentUserProvider.php line 122

      • According to this article, the User.php should be in App/DB/User folder and namespave should be AppDBUser;.

        • laurens

          Thank you for the fast response, That seems to did the trick regarding that issue, but the next issue it is spawning is: ErrorException in UserACL.php line 40:
          Trying to get property of non-object
          The issue is $this->role it cannot seem to find it, sorry about this, it’s rather confusing suddently working with traits, and I’m rather new with laravel, as far I have seen it is pretty awesome, but needs some learning curved, thank you for the fast awnsers! I appreciate it πŸ™‚

  • Harry Bosh

    I am interested in redirecting a user after login to a specific page based on their permission.
    where would one do this? here:
    return redirect()->route(‘home’);

    • After login, you may fire an event for example, Event::fire('userHasbeenLoggedIn', $user).

  • maku1337

    Laravel seems to dislike this line in hasPermission function, CheckPermission middleware:

    return !$this->forbiddenRoute($request) && $request->user()->can($required);

    Gives me this error:
    BadMethodCallException
    Call to undefined method IlluminateDatabaseQueryBuilder::can();

    I’d be glad if anyone is willing to give me some tips on how to solve this.

    • Brilliantio

      can() is defined in UserACL trait. Make sure that you added it.

  • Kristian Last

    What a fantastic article, we need more of this on the internet. Thanks for posting, really helping me get my head around Laravel 5

    • Really glad to here that, I’ll post a new article soon because Laravel - 5.1 allowes passing of parameters in Middleware.

      • Kristian Last

        Sounds great, can’t wait!

      • Lux

        Looking forwared to see this in L5.1 with full function, appreciate this !

      • wdog

        please L5.1 updates πŸ˜€

        • I’ll do it soon but it works as it is now @wdog666:disqus

  • User model is in app/DB or app/DB/User? Because the namespace is AppDBUser but you said the model was in app/DB. I’m confuse.

    Sorry for my english.

  • Michael Stratford

    Thank you, this is exactly what I was hoping to find. Looking forward seeing this up on Github.

  • clevino

    This is good but I have one question.Is it possible to have one login page for all the users eg index page will be the login page then the user will be redirected to their respective page depending on their role.I am using L5.1

  • Brilliantio

    Any thoughts on how to make this work for resource routes?

    • Sorry for the delay, I didn’t check actually. Anyways, can you share what difficulties you are facing to implement that @Brilliantio:disqus?

  • JosuΓ© Camelo dos Santos

    How do I return to the previous page returning a Session : flash?

    • Just use redirect()->back()->with('someKey', 'Some Value'), you didn’t mention about the context of this question, anyways. This should work. USe Session::get('someKey') to get the value on the next request.

      • JosuΓ© Camelo dos Santos

        If the user does not have access to certain action want to remain on the previous page . and of course I want to send a message warning the user that it does not have access to the resource .

        • In the handle method there is a line as given return redirect()->route('home');, by default the I redirect all non-permited users to home page but you may change it here and may use return redirect()->back()->with('message', 'Your Message here');.

          • JosuΓ© Camelo dos Santos

            Thank you so much.

          • You are welcome πŸ™‚

  • Mehran Hadidi

    first, Thank you for your amazing tutorial. was really helpful. Actually i have 2 questions. 1. Would you please make this tutorial available for Laravel 5.1. & Question 2: how can i use this tutorial for RESTful Resources. a lot of people have this question & ACL is very important layer in their projects but people like me font like to use third-party libraries because i cant control part by part. Thank your again. (Waiting for replay)

    • Thanks and apologies for my incomplete commitment about the new article for 5.1 but In Sha Allah I’ll do it soon. Regarding your first question, just use something like this:

      public function __construct()

      {
      $this->middleware(‘auth’); // To all methods

      $this->middleware(‘log’, [‘only’ => [‘create’, ‘update’]]); // Only specified methods

      $this->middleware(‘subscribed’, [‘except’ => [‘index’, ‘show’]]); // All but specified ones
      }

    • Check the new article (Middleware using Laravel 5.1.x).

  • Gospals

    Dudes, consider using caching here or store $permissions in property.

    // User model at checkPermission method
    $permissions = $this->getAllPernissionsFormAllRoles();

    This is executed each time when Blade checks for permission, every time it runs query and it’s HUDGE bottleneck.

    • Thanks @G@gospals:disqus for your input. I’ll check it soon πŸ™‚

  • Make some change in checkPermission method in UserACL trait:

    trait UserACL {

    protected static $permissions = null;

    // Other code/methods…

    protected function checkPermission(Array $perms = [])
    {
    static::$permissions = is_null(static::$permissions)
    ? $this->getAllPernissionsFormAllRoles()
    : static::$permissions;

    return count(array_intersect(static::$permissions, $perms)) || $this->runCallback($perms);
    }

    }

    Thnaks @gospals:disqus, for the comment πŸ™‚

  • Crosswire

    How would this work in L5.1 having models and resources? Creating a new route with a ACL overwrites these breaking them up

    • Use the middleware in __construct function of your resource controller:

      public function __construct()
      {
      $this->middleware(‘acl’);
      }

      Check more here:http://laravel.com/docs/5.1/controllers#controller-middleware

      • Crosswire

        Sorry i had to explain in more details, but i ment having ACL’s for each action like index/view, delete, edit, create, etc.

        i tried using a $this->middleware(‘acl:acl_user_create’, [‘only’ => [‘create’]);

        But that just didnt do anything it seems.

  • John Adrian Barraca

    Sir I’m new on laravel can I use this for group management?

    • Yes, definitely you can use it for groyp, roles are nothing but some groups with permissions @@johnadrianbarraca:disqus πŸ™‚

  • khudadad

    Can I create the above classes at app directory because all my models are in app?
    Thankl

    • Yes, you can but don’t forget to change the namespace in use statements.

Latest Blog