WordPress-Like Blog Laravel 5.7 and AdminLTE 3 (17) – Access Control
To make our application more secure, we need to limit users’ permission. To do this, we will implement Access Control List, or ACL or Role-Based Access Control (RBAC).
In this seventeenth part of creating WordPress-Like Blog using Laravel 5.7 and AdminLTE 3, we will :
- Create Access Control List
- Install Laratrust and Configure ACL Package
- Attach the Roles to Users
- Secure the Backend with the ACL
- Secure the Client Side
Create Access Control List
In our application, we will have five roles : The administrator, editor, and the author. Admin has full access. He is able to manage the posts, categories, and users.
The Editor has access to manage categories and post. They don’t have access to manage users.
The Author has only access to manage post they wrote.
Install Laratrust and Configure ACL Package
First, we need to install laratrust. From the terminal, type the installation command and wait until the installation has finished :
1 |
composer require "santigarcor/laratrust:5.0.*" |
Open config/app.php and add these two lines inside Providers and Aliases section, respectively :
1 2 |
Laratrust\LaratrustServiceProvider::class, 'Laratrust' => Laratrust\LaratrustFacade::class, |
Next, we generate the package by typing this command :
1 |
php artisan vendor:publish |
There will be two available files inside config folder. The laratrust.php and laratrust_seeder.php
Next, do some laratrust migration to create the tables that laratrust needs :
1 |
php artisan laratrust:migration |
This action will create a new migration file inside database/migrations folder, the laratrust_setup_tables.php.
Run the php artisan migrate command to create those tables. The tables created are roles, permissions, role_user, permission_user, and permission_role.
Create two new Models, the Role and Permission model inside App directory.
1 2 |
php artisan make:model Role php artisan make:model Permission |
Change the Role.php to :
1 2 3 4 5 6 7 8 9 |
<?php namespace App; use Laratrust\Models\LaratrustRole; class Role extends LaratrustRole { } |
Change the Permission.php to :
1 2 3 4 5 6 7 8 9 |
<?php namespace App; use Laratrust\Models\LaratrustPermission; class Permission extends LaratrustPermission { } |
Modify User.php model to include LaratrustTrait :
1 2 3 4 5 6 |
use Laratrust\Traits\LaratrustUserTrait; class User extends Authenticatable { use Notifiable; use LaratrustUserTrait; |
Attach The Roles to Users
We will create three roles, the admin, editor, and author. First create a seeder, RolesTableSeeder.php
1 |
php artisan make:seeder RolesTableSeeder |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<?php use Illuminate\Database\Seeder; use App\Role; use App\User; class RolesTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('roles')->truncate(); $admin = new Role(); $admin->name = "admin"; $admin->display_name = "Administrator"; $admin->save(); $editor = new Role(); $editor->name = "editor"; $editor->display_name = "Editor"; $editor->save(); $author = new Role(); $author->name = "author"; $author->display_name = "Author"; $author->save(); $user1 = User::find(1); //superadmin $user1->detachRole($admin); $user1->attachRole($admin); $user2 = User::find(2); //editor $user2->detachRole($editor); $user2->attachRole($editor); $user3 = User::find(3); //author $user3->detachRole($author); $user3->attachRole($author); } } |
Run the seeder from the terminal :
1 |
php artisan db:seed --class=RolesTableSeeder |
Secure the Backend with ACL
In the previous section we have created the role and permission for each user. Now we will try to implement this to our application. Modify layouts\backend\sidebar.blade.php so the menu visible only for the corresponding roles :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@role(['admin','editor']) <li class="nav-item has-treeview"> <a href="{{ route('backend.category.index') }}" class="nav-link"> <i class="nav-icon fa fa-pencil"></i> <p> Category </p> </a> </li> @endrole @role('admin') <li class="nav-item has-treeview"> <a href="{{ route('backend.user.index') }}" class="nav-link"> <i class="nav-icon fa fa-users"></i> <p> Users </p> </a> </li> @endrole |
This doesn’t solve our problem as the user with author role will still be able to access another role’s page by inserting the correct url.
Create a new middleware :
1 |
php artisan make:middleware CheckPermissionsMiddleware |
This new file will appear inside app\Http\Middleware directory.
Let’s register this middleware on app\Http\Kernel.php
1 2 3 |
protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'check-permissions' => \App\Http\Middleware\CheckPermissionsMiddleware::class, |
Now let’s implement this middleware to every backendcontroller by modify BackendController.php
1 2 3 4 5 |
public function __construct() { $this->middleware('auth'); $this->middleware('check-permissions'); } |
This is the CheckPermissionsMiddleware.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
<?php namespace App\Http\Middleware; use Closure; class CheckPermissionsMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { //get current user $currentUser = request()->user(); //get current action name $currentActionName = $request->route()->getActionName(); list($controller, $method) = explode('@',$currentActionName); $controller = str_replace(["App\\Http\\Controllers\\Backend\\","Controller"],"",$controller); $crudPermissionsMap = [ // 'create' => ['create','store'], // 'update' => ['edit', 'update'], // 'delete' => ['destroy', 'restore', 'forceDestroy'], // 'read' => ['index','view'], 'crud' => ['create', 'store', 'edit', 'update', 'destroy', 'restore', 'forceDestroy', 'index', 'view'], ]; $classesMap = [ 'Blog' => 'post', 'Category' => 'category', 'Users' => 'user', ]; foreach($crudPermissionsMap as $permission => $methods){ if(in_array($method,$methods) && isset($classesMap[$controller])){ $className = $classesMap[$controller]; //protect from accessing another users' post if($className == 'post' && in_array($method, ['edit','update', 'destroy', 'restore','forceDestroy'])){ if(($id = $request->route('blog')) && (!$currentUser->can('update-others-post') ||!$currentUser->can('delete-others-post'))){ $post = \App\Post::find($id); if($post->author_id !== $currentUser->id){ abort(403, "Forbidden Access!"); } } } elseif(!$currentUser->can("{$permission}-{$className}")){ abort(403, "Forbidden Access!"); } break; } } return $next($request); } } |
If the user login doesn’t have permission to perform action not listed in their role, the forbidden page will appear :
Secure the Client Side
After we successfully protect the backend permission, now we also need to do the same on the frontend section.
Let’s create a helper file to help us later. Create a new Helpers folder inside app Directory. Create permissions.php inside this folder :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<?php function check_user_permissions($request, $actionName = NULL, $id = NULL){ //get current user $currentUser = request()->user(); if($actionName) { $currentActionName = $actionName; }else{ $currentActionName = $request->route()->getActionName(); } //get current action name list($controller, $method) = explode('@',$currentActionName); $controller = str_replace(["App\\Http\\Controllers\\Backend\\","Controller"],"",$controller); $crudPermissionsMap = [ // 'create' => ['create','store'], // 'update' => ['edit', 'update'], // 'delete' => ['destroy', 'restore', 'forceDestroy'], // 'read' => ['index','view'], 'crud' => ['create', 'store', 'edit', 'update', 'destroy', 'restore', 'forceDestroy', 'index', 'view'], ]; $classesMap = [ 'Blog' => 'post', 'Category' => 'category', 'Users' => 'user', ]; foreach($crudPermissionsMap as $permission => $methods){ if(in_array($method,$methods) && isset($classesMap[$controller])){ $className = $classesMap[$controller]; //protect from accessing another users' post if($className == 'post' && in_array($method, ['edit','update', 'destroy', 'restore','forceDestroy'])){ $id = !is_null($id) ? $id : $request->route('blog'); if($id && (!$currentUser->can('update-others-post') ||!$currentUser->can('delete-others-post'))){ $post = \App\Post::withTrashed()->find($id); if($post->author_id !== $currentUser->id){ return false; } } } elseif(!$currentUser->can("{$permission}-{$className}")){ return false; } break; } } return true; } |
Autoload this file by opening composer.json and add the file inside autoload section :
1 2 3 4 5 6 7 8 9 10 |
"autoload": { "classmap": [ "database/seeds", "database/factories" ], "psr-4": { "App\\": "app/" }, "files": ["app/Helpers/permissions.php"] }, |
Open terminal and type this command :
1 |
composer dump-autoload |
Modify sidebar.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@if(check_user_permissions(request(), "Category@index")) <li class="nav-item has-treeview"> <a href="{{ route('backend.category.index') }}" class="nav-link"> <i class="nav-icon fa fa-pencil"></i> <p> Category </p> </a> </li> @endif @if(check_user_permissions(request(), "Users@index")) <li class="nav-item has-treeview"> <a href="{{ route('backend.user.index') }}" class="nav-link"> <i class="nav-icon fa fa-users"></i> <p> Users </p> </a> </li> @endif |
And modify CheckPermissionsMiddleware.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?php namespace App\Http\Middleware; use Closure; class CheckPermissionsMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if (! check_user_permissions($request)){ abort(403, "Forbidden Access"); } return $next($request); } } |
Open table.blade.php inside backend/blog and do some modification :
1 2 3 4 5 6 7 8 9 10 11 12 |
{!! Form::open(['method' => 'DELETE', 'route' => ['backend.blog.destroy', $post->id] ]) !!} @if(check_user_permissions(request(), "Blog@edit", $post->id)) <a href="{{ route('backend.blog.edit', $post->id) }}" class="btn btn-sm btn-default"><i class="fa fa-edit"></i></a> @else <a href="#" class="btn btn-sm btn-default disabled"><i class="fa fa-edit"></i></a> @endif @if(check_user_permissions(request(), "Blog@destroy", $post->id)) <button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-trash"></i></button> @else <button type="button" onclick="return false;" class="btn btn-sm btn-danger disabled"><i class="fa fa-trash"></i></button> @endif {!! Form::close() !!} |
Do the same with table-trash.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<td> {!! Form::open(['style' => 'display:inline-block' , 'method' => 'PUT', 'route' => ['backend.blog.restore', $post->id] ]) !!} @if(check_user_permissions(request(), "Blog@restore", $post->id)) <button title="Restore" class="btn btn-sm btn-default"><i class="fa fa-refresh"></i></button> @else <button title="Restore" class="btn btn-sm btn-default disabled"><i class="fa fa-refresh"></i></button> @endif {!! Form::close() !!} {!! Form::open(['style' => 'display:inline-block' ,'method' => 'DELETE', 'route' => ['backend.blog.force-destroy', $post->id] ]) !!} @if(check_user_permissions(request(), "Blog@forceDestroy", $post->id)) <button title="Hard Delete" type="submit" onclick="return confirm('Are you sure to delete the post?')" class="btn btn-sm btn-danger"><i class="fa fa-times"></i></button> @else <button title="Hard Delete" type="button" onclick="return false" class="btn btn-sm btn-danger disabled"><i class="fa fa-times"></i></button> @endif {!! Form::close() !!} </td> |
The current user only able to edit and delete their own post :
Before we end this part, let’s add another link to the right corner, to display only post by the current logged in user. Modify Backend\BlogController.php on statusList() and index() method :
1 2 3 4 5 6 7 8 9 10 |
private function statusList($request){ return [ 'own' => $request->user()->posts()->count(), 'all' => Post::count(), 'published' => Post::published()->count(), 'scheduled' => Post::scheduled()->count(), 'draft' => Post::draft()->count(), 'trash' => Post::onlyTrashed()->count(), ]; } |
1 2 3 4 5 6 |
elseif($status == 'own') { $posts = $request->user()->posts()->with('category','author')->latest()->paginate($this->limit); $allPostCount = Post::draft()->count(); } |
Github commit.
hi thank u for ur code i have been following ur code but a problem has occured the roles are working perfectly if a person does not have the permission to access but the role to admin and editor are not assigned correctly can u help me