Hey folks! So, picture this: you're crafting your admin panel and the need for proper access control hits you.
Admin panels without roles and permissions are like a party without bouncers – chaos waiting to happen. It's like giving everyone backstage access and waiting for disaster. We should return HTTP status code 403 to such unauthorized access events:
In this article, we'll learn to keep things organized, secure and ensure the right people have the right keys using Roles & permissions.
PermissionManager
To kick things off, I've equipped the Backpack project with the PermissionManager Addon.
- Install
composer require backpack/permissionmanager
- Follow a few steps and prepare your user model;
Boom! Your user model is now flexing some access control muscles:
Claiming Territory with Role & Permissions
When crafting roles and permissions, start by identifying distinct user responsibilities. Define them using Roles & Permissions CRUD:
- Permissions grant users access to specific actions or URLs;
- Roles are your squad leaders, grouping permissions together;
For instance, distinguish between Admins
and Editors
. Create permissions for each role(examples: manage articles
, manage tags
). Admin role holders can swagger through CRUD operations while Editors
only have access to:
-
Article
CRUD and can perform actions likedelete
andedit
on their posts only; - No access to
Tags
ORCategory
CRUD;
Remember, clarity is key! - Be granular and avoid unnecessary complexity.
Securing the Resources
To secure our admin panel, we need a bouncer at the gate. In our case, routes are the gates to our application and backpack_middleware()
is the bouncer. In routes/backpack/custom.php
, fortify your routes with backpack middleware:
Route::group([
'prefix' => config('backpack.base.route_prefix', 'admin'),
'middleware' => ['web', backpack_middleware()],
'namespace' => 'App\Http\Controllers\Admin',
], function () {
// admin routes
Route::crud('article', 'ArticleCrudController');
Route::crud('category', 'CategoryCrudController');
Route::crud('tag', 'TagCrudController');
});
We have placed a bouncer and he needs to know the rules to allow & deny user access, right?
Protecting Operations:
Each backpack operation comes with a simple access system. It works using an array $crud->settings['operation_name']['access']
which can either be true or false. All operations by default run CRUD::allowAccess('operation_name');
which toggle that variable to true.
You can add or remove elements to this access array in your setup() method using CRUD::denyAccess()
& CRUD::allowAccess()
.
The following code is good enough to check the current user's permission (manage articles
) and restrict access to its list
, show
, create
, update
and delete
operations of Article CRUD:
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
class ArticleCrudController extends CrudController
{
public function setup(){
// check permissions first
if(!backpack_user()->can('manage articles')){
// deny access to operations
CRUD::denyAccess(['list','show','create','update','delete']);
}
/**
... rest of the code...
**/
}
}
To restrict per entry basis, I would add the following to it:
CRUD::operation(['list','update','delete'], function() {
CRUD::setAccessCondition(['update', 'delete'], function ($entry) {
return $entry->author_id === backpack_user()->id ? true : false;
});
});
This will remove edit
& delete
buttons and restrict the form URLs for the posts which are not created by the currently logged-in user:
I find it cool, that small snippets take care of every single entry gate!
We also need to hide items from the menu. Admins should see only those menu items whose access they have. The following is a simple example of doing it using permissions:
// resources/views/vendor/backpack/ui/inc/menu_items.blade.php
@if(backpack_user()->can('manage articles'))
<x-backpack::menu-item title="Articles" icon="la la-newspaper-o" :link="backpack_url('article')" />
@endif
@if(backpack_user()->can('manage categories'))
<x-backpack::menu-item title="Categories" icon="la la-list" :link="backpack_url('category')" />
@endif
@if(backpack_user()->can('manage tags'))
<x-backpack::menu-item title="Tags" icon="la la-tag" :link="backpack_url('tag')" />
@endif
How to make this work for Custom Operations?
To set access control for your custom operation trait, use the following steps:
- Think of a key similar to your operation name or the action. Allow access to it while setting operation defaults via
CRUD::allowAccess('publish')
; - Toggle visibility of UI elements such as button via
@if ($crud->hasAccess('publish', $entry))
in the blade file; - Check access at the beginning of operation's function
CRUD::hasAccessOrFail('publish')
;
For example:
protected function setupPublishDefaults()
{
$this->crud->allowAccess('publish');
// Set operation defaults
}
public function publish()
{
CRUD::hasAccessOrFail('publish');
// Custom operation logic
}
Protecting custom routes:
Other than CRUD operations, you may have other admin panel routes to protect.
Here, we would use Spatie's permission
middleware. We can use it like can:list secrets
:
Route::group([
'prefix' => config('backpack.base.route_prefix', 'admin'),
+ 'middleware' => ['web','can:list secrets', backpack_middleware()],
], function () {
Route::get('secrets', 'SecretController@list');
});
Huuhoo! We restricted access to a custom route too!
You may want to Spatie's directives like @role
, @can
Directive like @can
, @role
uses default laravel authentication guard. You can have two scenarios here:
- Change the default guard in
config.auth.defaults.guard
tobackpack
. In this case, make sure your roles and permissios are using the backpack guard in the database. - Change the
config.backpack.base.guard
tonull
so backpack will also use theweb
guard. In this case, make sure web is the guard in the database for permissions and roles.
Depending on your project needs, one of the configuration could be the best to apply.
Conclusion
There you have it! Your admin panel is now a secure fortress. I hope you find this article an easy learn to implement access control on your admin panel.
When you need a quick fix, consult the following cheat codes:
// allow or deny access
CRUD::allowAccess(['list', 'create', 'delete']);
CRUD::denyAccess(['list', 'create', 'delete']);
CRUD::denyAllAccess();
// check access
CRUD::hasAccess('add'); // returns true/false
CRUD::hasAccess('add',$entry); // returns true/false
CRUD::hasAccessOrFail('add'); // throws 403 error
CRUD::hasAccessToAll(['create', 'update']); // returns true/false
CRUD::hasAccessToAny(['create', 'update']); // returns true/false
// allow or deny access depending on the entry
CRUD::operation(['list','update','delete'], function() {
CRUD::setAccessCondition(['update', 'delete'], function ($entry) {
return $entry->id===1 ? true : false;
});
});
For more advanced features, head over to the PermissionManager or Spatie's package docs.
Happy coding! 🚀
Top comments (0)