Easy roles and permissions in Laravel 5.4

Laravel comes with Authentication and Authorization out of the box, I have implemented many role and permissions based system in the past, using laravel, it’s peace of cake. In this post, we are going to implement a fully working and extensible roles and permissions on laravel 5.4. When we finish we will have a starter kit which we can use for our any future project which needs roles and permissions based access control (ACL).

Laravel Permissions

Although laravel comes with Policies to handle the authorization but I wanted to have an option to just create permissions in the database which we can manage by a UI in the admin panel, pretty standard. we can implement our own role and permission from scratch but I am going to use spatie/laravel-permission package for this. This package was inspired by Jeffrey ways screencast and it’s very well maintained and very easy to use. It has everything we need and plays very well with Laravel Gate and Policies implementations.

laravel-role-permissions-dashboard

Source Code

Scaffold app

Let’s get started by creating a fresh laravel app by running laravel new rpl or if you don’t have laravel command line tool, you can use composer to get it composer create-project --prefer-dist laravel/laravel rpl. rpl is the name of app which is an abbreviation for Role Permissions Laravel.

Once the installation is done, make necessary changes in .env so you can connect to a database.

Setup packages

We will use laravel authorization which comes bundled, let’s scaffold it using auth:make command. It will create a fully functional login, register and password reset features. Now that’s done, let’s pull packages we will need in this app.

Edit your composer.json to add below dependencies.

"require": {
        ...
        "spatie/laravel-permission": "^2.1",
        "laracasts/flash": "^3.0",
        "laravelcollective/html": "^5.3.0"
    },

Apart from permissions package, I have also grabbed flash to show notification alerts and laravelcollective html to create forms with the option to model bind them.

Now Add them in ServiceProvider and in aliases array, open config/app.php

'providers' => [
    ...
    Spatie\Permission\PermissionServiceProvider::class,        
    Laracasts\Flash\FlashServiceProvider::class,        
    Collective\Html\HtmlServiceProvider::class,
    ...
],

'aliases' => [
    ...
    'Form' => Collective\Html\FormFacade::class,
    'Html' => Collective\Html\HtmlFacade::class,
]

Cool, now let’s start by publishing migration which comes from permissions package, which will create tables for roles and permissions.

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"

Great, now we need to create our Resource, I am going to create Post, Role, Permission models with resource controller. User model is already present so we will use it.

// Create Post model with migration and resource controller
php artisan make:model Post -m -c --resource

// Create Role model and resource controller
php artisan make:model Role -c --resource

// Create Permission model and resource controller
php artisan make:model Permission -c --resource

Spatie\Permission already have role and permissions model, we will just extend them in order to do any changes if needed in future.

// Permission Model
class Permission extends \Spatie\Permission\Models\Permission { }

// Role Model
class Role extends \Spatie\Permission\Models\Role { }

We need to add HasRoles trait provided by the package to give the user all the power of laravel permissions.

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
    ...

Now we have all the boilerplate out of the way, let’s create simple database seeder so we can build and test our app, you can read more about advance database seeding in this post, open database/seeds/DatabaseSeeder.php and add this code.

public function run()
    {
        // Ask for db migration refresh, default is no
        if ($this->command->confirm('Do you wish to refresh migration before seeding, it will clear all old data ?')) {
            // Call the php artisan migrate:refresh
            $this->command->call('migrate:refresh');
            $this->command->warn("Data cleared, starting from blank database.");
        }

        // Seed the default permissions
        $permissions = Permission::defaultPermissions();

        foreach ($permissions as $perms) {
            Permission::firstOrCreate(['name' => $perms]);
        }

        $this->command->info('Default Permissions added.');

        // Confirm roles needed
        if ($this->command->confirm('Create Roles for user, default is admin and user? [y|N]', true)) {

            // Ask for roles from input
            $input_roles = $this->command->ask('Enter roles in comma separate format.', 'Admin,User');

            // Explode roles
            $roles_array = explode(',', $input_roles);

            // add roles
            foreach($roles_array as $role) {
                $role = Role::firstOrCreate(['name' => trim($role)]);

                if( $role->name == 'Admin' ) {
                    // assign all permissions
                    $role->syncPermissions(Permission::all());
                    $this->command->info('Admin granted all the permissions');
                } else {
                    // for others by default only read access
                    $role->syncPermissions(Permission::where('name', 'LIKE', 'view_%')->get());
                }

                // create one user for each role
                $this->createUser($role);
            }

            $this->command->info('Roles ' . $input_roles . ' added successfully');

        } else {
            Role::firstOrCreate(['name' => 'User']);
            $this->command->info('Added only default user role.');
        }

        // now lets seed some posts for demo
        factory(\App\Post::class, 30)->create();
        $this->command->info('Some Posts data seeded.');
        $this->command->warn('All done :)');
    }

    /**
     * Create a user with given role
     *
     * @param $role
     */
    private function createUser($role)
    {
        $user = factory(User::class)->create();
        $user->assignRole($role->name);

        if( $role->name == 'Admin' ) {
            $this->command->info('Here is your admin details to login:');
            $this->command->warn($user->email);
            $this->command->warn('Password is "secret"');
        }
    }

Now add defaultPermissions in Permissions Model which we have created.

public static function defaultPermissions()
{
    return [
        'view_users',
        'add_users',
        'edit_users',
        'delete_users',

        'view_roles',
        'add_roles',
        'edit_roles',
        'delete_roles',

        'view_posts',
        'add_posts',
        'edit_posts',
        'delete_posts',
    ];
}

Post model factory contains only 2 fileds, title and body. You should setup the migration and factory. Now run the seeder using php artisan db:seed it should give an admin user which you can use to login.

Now you can login with admin user but there is no access control in place, I will create the User Resource first.

Create the UserController and add below code.

public function index()
{
    $result = User::latest()->paginate();
    return view('user.index', compact('result'));
}

public function create()
{
    $roles = Role::pluck('name', 'id');
    return view('user.new', compact('roles'));
}

public function store(Request $request)
{
    $this->validate($request, [
        'name' => 'bail|required|min:2',
        'email' => 'required|email|unique:users',
        'password' => 'required|min:6',
        'roles' => 'required|min:1'
    ]);

    // hash password
    $request->merge(['password' => bcrypt($request->get('password'))]);

    // Create the user
    if ( $user = User::create($request->except('roles', 'permissions')) ) {
        $this->syncPermissions($request, $user);
        flash('User has been created.');
    } else {
        flash()->error('Unable to create user.');
    }

    return redirect()->route('users.index');
}

public function edit($id)
{
    $user = User::find($id);
    $roles = Role::pluck('name', 'id');
    $permissions = Permission::all('name', 'id');

    return view('user.edit', compact('user', 'roles', 'permissions'));
}

public function update(Request $request, $id)
{
    $this->validate($request, [
        'name' => 'bail|required|min:2',
        'email' => 'required|email|unique:users,email,' . $id,
        'roles' => 'required|min:1'
    ]);

    // Get the user
    $user = User::findOrFail($id);

    // Update user
    $user->fill($request->except('roles', 'permissions', 'password'));

    // check for password change
    if($request->get('password')) {
        $user->password = bcrypt($request->get('password'));
    }

    // Handle the user roles
    $this->syncPermissions($request, $user);

    $user->save();
    flash()->success('User has been updated.');
    return redirect()->route('users.index');
}

public function destroy($id)
{
    if ( Auth::user()->id == $id ) {
        flash()->warning('Deletion of currently logged in user is not allowed :(')->important();
        return redirect()->back();
    }

    if( User::findOrFail($id)->delete() ) {
        flash()->success('User has been deleted');
    } else {
        flash()->success('User not deleted');
    }

    return redirect()->back();
}

private function syncPermissions(Request $request, $user)
{
    // Get the submitted roles
    $roles = $request->get('roles', []);
    $permissions = $request->get('permissions', []);

    // Get the roles
    $roles = Role::find($roles);

    // check for current role changes
    if( ! $user->hasAllRoles( $roles ) ) {
        // reset all direct permissions for user
        $user->permissions()->sync([]);
    } else {
        // handle permissions
        $user->syncPermissions($permissions);
    }

    $user->syncRoles($roles);
    return $user;
}

Everything is commented and self-explanatory, it’s a simple controller to perform CRUD operation with user level permission override so you can give access to certain permissions for the specific user. Now register the route for all the resource controllers.

Route::group( ['middleware' => ['auth']], function() {
    Route::resource('users', 'UserController');
    Route::resource('roles', 'RoleController');
    Route::resource('posts', 'PostController');
});

Go ahead and create the PostController by yourself, you can always access the source code if need help.

Authorization

This is the main part, authorization will be in 2 level, first is at the controller level and second in view level. In view, if you don’t have permission to add_users then it doesn’t make sense to show Create button. this can be done using @can('add_users') directive in blade template.

admin-role-users-view

...
@can('add_users')
    <a href="{{ route('users.create') }}" class="btn btn-primary btn-sm">
        <i class="glyphicon glyphicon-plus-sign"></i> Create
    </a>
@endcan
...

Similarly, if you don’t have access to edit_users you should not see the edit button or delete button in the table.

user-role-users-view

<table class="table table-bordered table-striped table-hover" id="data-table">
    <thead>
    <tr>
        ....
        <th>Created At</th>
        @can('edit_users', 'delete_users')
            <th class="text-center">Actions</th>
        @endcan
    </tr>
    </thead>
    <tbody>
    @foreach($result as $item)
        <tr>
            ...
            @can('edit_users')
            <td class="text-center">
                // action buttons
            </td>
            @endcan
        </tr>
    @endforeach
    </tbody>
</table>

Now that’s fine, but some malicious user can still directly visit the URL and he will be able to access the protected route. To prevent that we need protection on controller@method level.

Authorizable Trait

We can add $user->can() check in every method to handle authorization but it will make it more difficult to maintain and since we will be using this in multiple resource controller it will be good to extract out this logic in a trait which will handle the authorization automatically, sounds good! let’s do it.

namespace App;

trait Authorizable
{
    private $abilities = [
        'index' => 'view',
        'edit' => 'edit',
        'show' => 'view',
        'update' => 'edit',
        'create' => 'add',
        'store' => 'add',
        'destroy' => 'delete'
    ];

    /**
     * Override of callAction to perform the authorization before
     *
     * @param $method
     * @param $parameters
     * @return mixed
     */
    public function callAction($method, $parameters)
    {
        if( $ability = $this->getAbility($method) ) {
            $this->authorize($ability);
        }

        return parent::callAction($method, $parameters);
    }

    public function getAbility($method)
    {
        $routeName = explode('.', \Request::route()->getName());
        $action = array_get($this->getAbilities(), $method);

        return $action ? $action . '_' . $routeName[0] : null;
    }

    private function getAbilities()
    {
        return $this->abilities;
    }

    public function setAbilities($abilities)
    {
        $this->abilities = $abilities;
    }
}

In this trait we have to override the callAction method which gets called by the router to trigger respective method on the resource controller, that’s good place to check the permission, so we get the route name in users case it will be users.index, users.store, users.update etc.. we have mapped the abilities to our resource controller route naming conventions.

what happens is when a user visits route named users.index, it gets translated into view_users ability to check against in authorize($ability) method by getAbility() method, when user visits edit page route users.edit it will be translated as edit_users and so on. by extracting this logic in a Trait we will be able to apply authorization on any resource controller we wanted.

Add our trait on UserController.

use App\Authorizable;

class UserController extends Controller
{
    use Authorizable;
    ...

That’s it, your controller is protected, the only user who has permissions to visit certain route can access it.

When user doesn’t have permission they get AuthorizationException exception, which is not very friendly to end user, let’s handle this in global Handler so we can display a notification and send the user back to dashboard if the try to visit some route which they don’t suppose to access.

AuthorizationException Exception Handler

Open the app/Exceptions/Handler.php and add this in render method

public function render($request, Exception $exception)
{
    if ($exception instanceof AuthorizationException) {
        return $this->unauthorized($request, $exception);
    }

    return parent::render($request, $exception);
}

private function unauthorized($request, Exception $exception)
{
    if ($request->expectsJson()) {
        return response()->json(['error' => $exception->getMessage()], 403);
    }

    flash()->warning($exception->getMessage());
    return redirect()->route('home');
}

This will redirect back to home route with flash notification if user doesn’t have access to the action.

Role Management

Let’s create the roles management resource controller. which admin can use to create the new role and give or change permission to them.

class RoleController extends Controller
{
    use Authorizable;

    public function index()
    {
        $roles = Role::all();
        $permissions = Permission::all();

        return view('role.index', compact('roles', 'permissions'));
    }

    public function store(Request $request)
    {
        $this->validate($request, ['name' => 'required|unique:roles']);

        if( Role::create($request->only('name')) ) {
            flash('Role Added');
        }

        return redirect()->back();
    }

    public function update(Request $request, $id)
    {
        if($role = Role::findOrFail($id)) {
            // admin role has everything
            if($role->name === 'Admin') {
                $role->syncPermissions(Permission::all());
                return redirect()->route('roles.index');
            }

            $permissions = $request->get('permissions', []);
            $role->syncPermissions($permissions);
            flash( $role->name . ' permissions has been updated.');
        } else {
            flash()->error( 'Role with id '. $id .' note found.');
        }

        return redirect()->route('roles.index');
    }
}

Now lets add the views for role, create resources/views/role/index.blade.php with below markup.

@extends('layouts.app')

@section('title', 'Roles & Permissions')

@section('content')
    <!-- Modal -->
    <div class="modal fade" id="roleModal" tabindex="-1" role="dialog" aria-labelledby="roleModalLabel">
        <div class="modal-dialog" role="document">
            {!! Form::open(['method' => 'post']) !!}

            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
                    <h4 class="modal-title" id="roleModalLabel">Role</h4>
                </div>
                <div class="modal-body">
                    <!-- name Form Input -->
                    <div class="form-group @if ($errors->has('name')) has-error @endif">
                        {!! Form::label('name', 'Name') !!}
                        {!! Form::text('name', null, ['class' => 'form-control', 'placeholder' => 'Role Name']) !!}
                        @if ($errors->has('name')) <p class="help-block">{{ $errors->first('name') }}</p> @endif
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>

                    <!-- Submit Form Button -->
                    {!! Form::submit('Submit', ['class' => 'btn btn-primary']) !!}
                </div>
                {!! Form::close() !!}
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col-md-5">
            <h3>Roles</h3>
        </div>
        <div class="col-md-7 page-action text-right">
            @can('add_roles')
                <a href="#" class="btn btn-sm btn-success pull-right" data-toggle="modal" data-target="#roleModal"> <i class="glyphicon glyphicon-plus"></i> New</a>
            @endcan
        </div>
    </div>


    @forelse ($roles as $role)
        {!! Form::model($role, ['method' => 'PUT', 'route' => ['roles.update',  $role->id ], 'class' => 'm-b']) !!}

        @if($role->name === 'Admin')
            @include('shared._permissions', [
                          'title' => $role->name .' Permissions',
                          'options' => ['disabled'] ])
        @else
            @include('shared._permissions', [
                          'title' => $role->name .' Permissions',
                          'model' => $role ])
            @can('edit_roles')
                {!! Form::submit('Save', ['class' => 'btn btn-primary']) !!}
            @endcan
        @endif

        {!! Form::close() !!}

    @empty
        <p>No Roles defined, please run <code>php artisan db:seed</code> to seed some dummy data.</p>
    @endforelse
@endsection

Let’s add resources/views/shared/_permissions.blade.php template.

<div class="panel panel-default">
    <div class="panel-heading" role="tab" id="{{ isset($title) ? str_slug($title) :  'permissionHeading' }}">
        <h4 class="panel-title">
            <a role="button" data-toggle="collapse" data-parent="#accordion" href="#dd-{{ isset($title) ? str_slug($title) :  'permissionHeading' }}" aria-expanded="{{ $closed or 'true' }}" aria-controls="dd-{{ isset($title) ? str_slug($title) :  'permissionHeading' }}">
                {{ $title or 'Override Permissions' }} {!! isset($user) ? '<span class="text-danger">(' . $user->getDirectPermissions()->count() . ')</span>' : '' !!}
            </a>
        </h4>
    </div>
    <div id="dd-{{ isset($title) ? str_slug($title) :  'permissionHeading' }}" class="panel-collapse collapse {{ $closed or 'in' }}" role="tabpanel" aria-labelledby="dd-{{ isset($title) ? str_slug($title) :  'permissionHeading' }}">
        <div class="panel-body">
            <div class="row">
                @foreach($permissions as $perm)
                    <?php
                        $per_found = null;

                        if( isset($role) ) {
                            $per_found = $role->hasPermissionTo($perm->name);
                        }

                        if( isset($user)) {
                            $per_found = $user->hasDirectPermission($perm->name);
                        }
                    ?>

                    <div class="col-md-3">
                        <div class="checkbox">
                            <label class="{{ str_contains($perm->name, 'delete') ? 'text-danger' : '' }}">
                                {!! Form::checkbox("permissions[]", $perm->name, $per_found, isset($options) ? $options : []) !!} {{ $perm->name }}
                            </label>
                        </div>
                    </div>
                @endforeach
            </div>
        </div>
    </div>
</div>

If you visit now /roles you can find will be able to manage the roles and permission.

roles-and-permissions

Permissions Management

Permissions are not going to be changed very often in most cases, you can just add them directly into the database. I am leaving the implementation for this. It’s simple CRUD, if you wanted you can implement it. One thing we can do to create a command which we can run to create permissions, something like php artisan auth:permission tasks, which will create 'view_tasks', 'add_tasks', 'edit_tasks', 'delete_tasks' the permissions for tasks model, and if we pass --remove it will delete permissions on it.

Create our command by running php artisan make:command AuthPermissionCommand.

class AuthPermissionCommand extends Command
{
    protected $signature = 'auth:permission {name} {--R|remove}';
    ...

    public function handle()
    {
        $permissions = $this->generatePermissions();

        // check if its remove
        if( $is_remove = $this->option('remove') ) {
            // remove permission
            if( Permission::where('name', 'LIKE', '%'. $this->getNameArgument())->delete() ) {
                $this->warn('Permissions ' . implode(', ', $permissions) . ' deleted.');
            }  else {
                $this->warn('No permissions for ' . $this->getNameArgument() .' found!');
            }

        } else {
            // create permissions
            foreach ($permissions as $permission) {
                Permission::firstOrCreate(['name' => $permission ]);
            }

            $this->info('Permissions ' . implode(', ', $permissions) . ' created.');
        }

        // sync role for admin
        if( $role = Role::where('name', 'Admin')->first() ) {
            $role->syncPermissions(Permission::all());
            $this->info('Admin permissions');
        }
    }

    private function generatePermissions()
    {
        $abilities = ['view', 'add', 'edit', 'delete'];
        $name = $this->getNameArgument();

        return array_map(function($val) use ($name) {
            return $val . '_'. $name;
        }, $abilities);
    }
    
    private function getNameArgument()
    {
        return strtolower(str_plural($this->argument('name')));
    }
}

Our command is ready, next let’s register it in Kernel, open the app/Console/Kernel.php and add.

App\Console\Commands\AuthPermissionCommand;

class Kernel extends ConsoleKernel
{
    protected $commands = [
        AuthPermissionCommand::class
    ];
    ...

The auth:permission command is ready, now you can run it to add/remove permissions.

If you added permissions manually in the db, don’t forget to run php artisan cache:forget spatie.permission.cache, otherwise new permissions wont work.

Finally, we have a starter kit which you can use for any new project required roles and permissions. Check the source code & I hope you found it useful, let me know in the comments if you have any question.

Source Code

  • Very nice, thanks for writing this. I’ll add a link to this tutorial in the readme of the package.

    • Saqueib

      That will be great, thanks for simple and awesome package for laravel permissions

  • bambamboole

    Really nice tutorial !
    This helped me a lot with handling ACL in my new Application

  • Nice Tutorial. It is very helpful.

  • bambamboole

    I added one custom permission directly to the database (not crud permission) and the template breaks. Do you have any solution for this?

    • Saqueib

      You can always fallback to @can('your_custom_permissions') in blade template. same is true for controller Auth::user()->can('your_custom_permissions') or you can use authorized like this


      $this->authorize('your_custom_permissions');

      I would suggest pick a naming convention for route and permission and stick to it, you can also match the $abilities array in Authorizable traits line number 15 for your specific need.

      If you need any further help, please share your custom route and some permissions you wanted to add. by default a permission name is made of {controller_method}_{route_name} see the trait.

      • bambamboole

        I just added a new permission as an example for a custom permission and the route with the role/permissions form broke.
        I know how I can authorize against a custom permission, but i want the custom permission in the permissions view with the other permissions 🙂

        • Mohd Saqueib

          ok, if you can share the permissions you wanted to add, i will try to reproduce it at my end to better understanding.

          I need to add these directly in database right?

        • Saqueib

          if you can give me the permissions you have added that will be very helpful to reproduce this error at my end.

          • bambamboole

            I just added a new permission via Sequel Pro called custom_permission. nothing more.
            After adding this permission the permission template breaks…

          • Josh

            Worked out the issue. After adding a permission via Sequal Pro you need to run `php artisan cache:forget spatie.permission.cache`

          • Josh

            Heya, If I try and add a custom permission called ‘access_staff_panel’ it would break the permissions blade saying . “There is no permission named `access_staff_panel` for guard `web`.” As mentioned I did this just using Sequal Pro, how would I go about adding a custom permission that I can assign to roles + users?

  • Nice tutorial, thank you! It has helped me get ACL working on my new app. I am experiencing one small issue though: Trying to use the auth:permission artisan command works for creating new permissions, but trying to use the –R or remove option results in “The “–R” option does not exist.” or “Too many arguments, expected arguments “command” “name”. respectively. Any ideas why I can’t specify remove?

    • Saqueib

      hey Chris, you need to user

      php artisan auth:permission Comment -R
      or
      php artisan auth:permission Comment –remove

      to remove permissions.

  • Thank You very much 🙂

  • Satish Reddy

    excellent tutorial, it showed me the way. i struck here, where in migrations when i see migrations it can create model_has_roles and model_has_permissions tables. But when i run the code i am getting errors like ‘base table or view not found. we don’t have tables like user_has_roles and user_has_permissions table. Please suggest. thank you in advance. thank you

    • Saqueib

      Its seems you forgot to ran the migrations, please try running it but

      php artisan migrate
      or
      php artisan migrate:refresh

      or if you have cloned code from repo you can run the php artisan db:seed it will prompt u to refresh the migration.

      Let me know how it goes

  • madsem

    Hey Saqueib,

    very nice tutorial! Thanks a lot, will use this for my new app.

    Quick question: As far as I can see, there is nothing prepared to make queries in a Controller based on user permissions.

    For example on an edit view, include roles for admins, but not for normal users.

    How would you do that?

    Just rely on @can(‘admin’) in Blade, or actually do two seperate Eloquent queries for each user role to only include the data they are allowed to see?

    • Saqueib

      hi @madsem:disqus you can query or check for permission in controller using $user->can('assign_roles')
      .

      To show the user roles for admin I would create a custom permission and assign it to admin role, and later in blade view i would check it by @can(‘assign_roles’) directive. don’t forget to run php artisan cache:forget spatie.permission.cache to clear the cache.

      • madsem

        got it, thanks 🙂

        I think I wasn’t 100% clear with my question.

        I meant: Would you even make Eloquent queries based on user roles, or just one query for all data a View needs, and then only use @can(‘permission’) in the Blade view to include/exclude data, or would you make multiple DB queries, one for each role? Like what do you think is best practice here 🙂

      • madsem

        Saqueib I believe there are two small errors in the Git repo.
        “`
        UserController.php

        } else {

        // handle permissions

        $user->syncPermissions($permissions);

        }
        “`

        should be:
        “`

        }

        // handle permissions

        $user->syncPermissions($permissions);

        “`

        otherwise permissions are not correctly updated when you override them on a per user basis.

        And in Handler.php you have commented out the render() method, which causes the auth check to throw an error when a user does an unauthorized action.

  • Jonathan Espinosa

    Great Tutorial, I am new in laravel and I am learning how to manage the roles and permissions situation, Can you help to understand how to add new permissions and roles? and to martch it with the controllers or routes, I can understand that. I hope you can help me. sorry for this noob cuestion.

    • Saqueib

      Thanks, If you clone the repo you can add Roles by running php artisan db:seed. It will ask you to type roles in comma separate format, like ‘Admin,User’ will create these 2 roles. or you can manually add a Role directly in roles mysql table.

      For permissions you can use another command php artisan auth:permission Post, it will create permissions for Post model.

      Now when you have got roles and permissions ready you can add Authorizable trait on a any resource controller to protect it via permissions.


      class PostController extends Controller
      {
      use Authorizable;

      I hope it helps you. If you have any question let me know

  • Morshed Khan Rana

    When I have upload live server then shown this error ,,

    (1/1) FatalErrorException
    syntax error, unexpected ‘:’, expecting ‘;’ or ‘{‘
    in PermissionRegistrar.php (line 33)

    Please help me , as soon as possible !!!

    http://pos.hello-sylhet.com/

    • Saqueib

      It seems something wrong with permissions, directories within the storage and the bootstrap/cache directories should be writable by your web server. Also run composer update on server

      • Morshed Khan Rana

        when I run this project in localhost then work perfectly but when I upload this project host in live server then given this error ,, please tell me how can I fix it !

        (1/1) FatalErrorException
        syntax error, unexpected ‘:’, expecting ‘;’ or ‘{‘
        in PermissionRegistrar.php (line 33)

        http://postest.hello-sylhet.com/
        please reply !!

        • Saqueib

          Your web server is running old version on PHP 5.3, try updating it to PHP >= 5.6.4, this should fix it

  • Great tut. Absolutely try this for a side project. Thanks S.

  • tahjid ashfaque

    I am total newbie in laravel, I tried following this tutorial, when I run php artisan db:seed, I am getting this error,

    [SymfonyComponentDebugExceptionFatalThrowableError]
    Class ‘Permission’ not found
    but my Permission model is defined , please help.
    Thank You

  • Shakir Bhatt

    How can i hide topnav menus.

  • ADEBOYE OLAITAN MERCY

    Great tutorial, I have everything working until i get ‘This action is authorized’ for a particular route which i have given permission to

    Am i missing something

  • ADEBOYE OLAITAN MERCY

    When i open route like /patient/{patient},
    Everything is fine
    But when i visit routes like
    /patient/{patient}/update,

    If gives
    ‘This action is authorized’

    Help please

    • Saqueib

      Authorizable trait will work only if you follow the Laravel resource controller, for example route for update should look like this


      PUT|PATCH | /patient/{patient}

      You can always use authorize('edit_patient') in your controller methods if you don’t want to follow resourceful controller schema

      • Nabiullah Khan

        Hi,
        for me route is ok but still give “This action is unauthorized.”

  • Suresh Prajapati

    how to login as admin ?

    • Mohd Saqueib

      When you run php artisan db:seed on successful run it will give you Admin email id, and default password is secret

    • Saqueib

      When you run php artisan db:seed on successful run it will give you Admin email id, and default password is “secret”

  • RichLove

    Hi. Great tutorial, I’m still working through it but encountered an issue. The check here: if ($exception instanceof AuthorizationException) in Handler.php is returning false when there is an auth error. When I dump $exception it looks like an instance of AuthorizationException so not sure why the check return false. I noticed also this line of code is commented out in the project github repo.

    https://github.com/saqueib/roles-permissions-laravel/blob/master/app/Exceptions/Handler.php#L48

    • RichLove

      My bad. Forgot to add ‘use IlluminateAuthAccessAuthorizationException;’

      • Saqueib

        it happens always, I bet you are using sublime text, in PHPStorm you will get instant hint if you forgot to import any class.

        • RichLove

          Ah, I am using SublimeText. I tried PHPStorm and liked it but need to invest a little time to get to know it better. I went back to Sublime for simplicity (but less power maybe). Would you recommend switching to PHPStorm?

  • RichLove

    Fantastic tutorial and base ACL, thank you!

  • Nabiullah Khan

    Hi Sequeib,
    Thanks for wonderful tutorial, please help me.

    when I use “php artisan migrate” so it gives error

    [IlluminateDatabaseQueryException]

    SQLSTATE[42000]: Syntax error or access violation: 1103 Incorrect table name ” (SQL: create table “ (id int unsigned not null auto_increment primary key, `name`

    varchar(191) not null, `guard_name` varchar(191) not null, `created_at` timestamp null, `updated_at` timestamp null) default character set utf8mb4 collate utf8mb4_un

    icode_ci)

    [PDOException]

    SQLSTATE[42000]: Syntax error or access violation: 1103 Incorrect table name ”

    • Saqueib

      Its seems you are not getting the table name from config file, please publish the vendor config using this.

      php artisan vendor:publish --provider="SpatiePermissionPermissionServiceProvider" --tag="config"

      Let me know how it goes.

  • somenet77

    I have successfully configure the above example and worked fine for laravel resource controller. But when i have added custom method in the controller the 403 error doesn’t work no protection over such url. But when i have added the such method in Abilities array in Authorizable traits it worked fine. But my question is, it is not good to added every custom method in the Abilities array. So what is the solution.

    • Saqueib

      You don’t need to add in abilities array, that array is used if you want to change naming convention of routes altogether, by default its set for resource controller naming convention.

      For any custom method authorization you should use


      public function customeControllerAction() {

      $this->authorize('your_custom_permission');

      ...
      }

      I hope this helps

      • somenet77

        Thanks now work fine. But still confuse in AuthorizationException Exception Handler when i have added the code above you wrote in Handler.php the permission doesn’t work, every user has access all url although i doesn’t give the permission for those user. What is the problem ?

        • Saqueib

          Its simple, AuthorizationException Handler checks if its an Unauthorized Exception it returns redirect to home with a flash mag, and if its an ajax call it will return unauthorize msg as json. thats all its doing

  • TZY

    (1/1) HttpException
    This action is unauthorized.

    May i know how to solve this error?

    What should i do if i want user to be able to access the ACL?

    • Mohd Saqueib

      You need to give user permissions to access that route, log in as admin and give permission. If you dont want ACL on a certain controller just remove Authorizable trait from controller and access will be allowed by default.

      If you need any other help let me know with some code where you are getting this unauthorized exception.

    • Saqueib

      You need to give user permissions to access that route, log in as admin and give permission. If you dont want ACL on a certain controller just remove Authorizable trait from controller and access will be allowed by default.

      If you need any other help let me know with some code where you are getting this unauthorized exception.

      • TZY

        Actually i am recovering a old project that use laravel-permission package, now i am able to access the role and user by commenting the Authorizable trait. Even if i login as admin i am not able to make changes in the role and user view.

        • Saqueib

          try clearing cache by running php artisan cache:forget spatie.permission.cache. share some code so I can help you if problem still exists.

  • Ivan

    Hi there, when I run the php artisan db:seed, everything works normal till the:
    “Enter roles in comma separate format. [Admin,User]:”

    when I entered the roles, it show something like this:
    [SymfonyComponentDebugExceptionFatalThrowableError]
    Parse error: syntax error, unexpected ‘;’, expecting function (T_FUNCTION)

    I rechecked the DatabaseSeeder.php and everything look fine, but maybe I miss something

    and btw, nice tutorial, and thank you.

  • The Mistico

    I have a little problem

    (1/1) HttpException
    This action is unauthorized.

    This happens when i open users view, i followed all this tutorial and i was comparing between your source code and mine, I can’t find the solution.

    https://github.com/Majunko/Test

    What can i do?

    • Saqueib

      Thanks for sharing code, here is the issue, you have translated the names of routes and they are not updated in view files. Change names in these files.


      # routes/web.php
      Route::resource('usuarios', 'UserController'); // change users to usuarios

      // update the route names in views
      # resources/views/layouts/app.blade.php line:51-57
      @can('ver_usuarios')


      Users

      @endcan

      # resources/views/user/index.blade.php line:12,43
      Create

      @include('shared._actions', [
      'entity' => 'usuarios',
      'id' => $item->id
      ])

      This will show you the users list, but you will be needed to update it everywhere, in Form action and in other views also.

      created a pull request, merge it on github to get the fix.

      I hope it helps, let me know how it goes

      • The Mistico

        Yeah it helps a lot, Thank you Saqueib for taking your time for help me. Now everything goes very fine.

        Great tutorial, you’re amazing.
        Regards.