I'm trying to add multiple roles to a route group in my web file in Laravel.
I would like protect certain routes based on the users Role like the admin section.
Some Routes need multiple roles for access.
Example Roles:
SuperAdmin
Admin
Moderator
etc..
This works and I can go to the admin panel if loged in and have One Role only in the route group.
Route::middleware(['auth','role:SuperAdmin'])->group(function () {
Secret routes..
});
This does not work if I try and add more roles to to the route group
like this: Route::middleware(['auth','role:SuperAdmin|Admin|Moderator'])->group(function () {
Note the 'role:SuperAdmin|Admin|Moderator'
This is the RoleMiddleware file:
public function handle($request, Closure $next, $role, $permission = null)
{
if (!$request->user()->hasRole($role)) {
abort(404);
}
if ($permission !== null && !$request->user()->can($permission)) {
abort(404);
}
return $next($request);
}
The User Class has a trait called use:HasPermissionTrait
class User extends Authenticatable
{
use Notifiable, HasPermissionsTrait, Billable;
In that HasPermissionsTrait I have the following:
I have permission setup fine, just focusing on the Roles in this file. I moved the Role logic to the top.
use App\{Role, Permission};
trait HasPermissionsTrait
{
public function hasRole(...$roles)
{
foreach ($roles as $role) {
if ($this->roles->contains('name', $role)) {
return true;
}
}
return false;
}
public function roles()
{
return $this->belongsToMany(Role::class, 'users_roles');
}
... // Permission logic start here...
}
Worth Mentioning:
The tables for roles are:
users
roles
users_roles (I know the correct naming convention should be roles_users, but not relevant here)
Just need to know how to get this working in the route group:
'role:SuperAdmin|Admin|Moderator'
Solution:
RoleMiddleware file:
public function handle($request, Closure $next, $role, $permission = null)
{
$role = strtolower( $request->user()->hasRole($role));
$allowed_roles = array_slice(func_get_args(), 2);
if (!$request->user()->hasRole(in_array($role, $allowed_roles))) {
abort(404);
}
if ($permission !== null && !$request->user()->can($permission)) {
abort(404);
}
return $next($request);
}
Can do route group like this.
Route::middleware(['auth','role:SuperAdmin|Admin'])->group(function () {
This is what I did in my CheckRole Middleware
public function handle($request, Closure $next) {
// I'm using the api guard
$role = strtolower( request()->user()->type );
$allowed_roles = array_slice(func_get_args(), 2);
if( in_array($role, $allowed_roles) ) {
return $next($request);
}
throw new AuthenticationException();
}
And in my router file
Route::group(["middleware" => "role:admin,worker"], function() {
});
This might not be the perfect solution, at least it works for me.
Route::middleware(['auth'])->group(function () {
//Routes available to super admin
Route::middleware(['role:SuperAdmin'])->group(function () {
//write route hear
});
//Routes available to SuperAdmin, Admin and Moderator
Route::middleware(['role:SuperAdmin|Admin|Moderator'])->group(function () {
//write route hear
})
});
try this way to define route group of routing. I already use this syntax and it's work.
define auth in parent middleware group and roles defined in the child middleware groupe.
explode the roles in your middleware and check against the available roles
public function handle($request, Closure $next, $role, $permission = null)
{
$roles = is_array($role)
? $role
: explode('|', $role);
if (!$request->user()->hasRole($roles)) {
abort(404);
}
if ($permission !== null && !$request->user()->can($permission)) {
abort(404);
}
return $next($request);
}
Related
I am working on a project in which I have three type of users Admin and user1 and user2. I want user1 and user2 to able to use certain features in application only if the admin has assigned an invoice to them. I have tried using helper function given below.
$invoice = Invoice::pluck('user_id')->toArray();
if (Auth::user()->admin == 1 || in_array(Auth::user()->id, $invoice)) {
return 1;
} else {
return 0;
}
but this does not work fine. I'll have to place it before every method of a controller in order to restrains users to use that feature. Is there any thing else I can do?
Any Better Approach for this?
You can use middlewares.
Create your middleware with
php artisan make:middleware UserWithInvoiceMiddleware
Then open your file in app/Http/Middleware/UserWithInvoiceMiddleware.php, and add this to the handle method:
public function handle($request, Closure $next, ...$guards)
{
$user = auth()->user();
$invoice = Invoice::pluck('user_id')->toArray();
if ($user->admin || in_array($user->id, $invoice)) {
return $next($request);
}
return response()->json(['message' => 'Request not authorized.'], 401);
}
Also, you can create a relation in your user model with the Invoice model:
public function invoice()
{
return $this->hasOne(Invoice::class);
}
Then, you can simplify your middleware using this relation:
public function handle($request, Closure $next, ...$guards)
{
if (auth()->user()->admin || auth()->user()->has('invoice')) {
return $next($request);
}
return response()->json(['message' => 'Request not authorized.'], 401);
}
You have to register your middleware in app/Http/Kernel.php, under the $routeMiddleware array:
protected $routeMiddleware = [
...
'user-with-invoice' => App\Http\Middleware\UserWithInvoiceMiddleware::class,
];
Then, you can protect your routes with this middleware, adding a ->middleware('user-with-invoice') to the routes where the user has to be an admin or have an invoice:
Route::get('/example', ExampleController::class)->middleware('user-with-invoice');
you can use make a middleware and pass requests throw it to check if the user is authorized to do that or not.
class SomeMidllewareName
{
/**
* Handle an incoming request.
*
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$invoice = Invoice::pluck('user_id')->toArray();
if (1 == Auth::user()->admin || in_array(Auth::user()->id, $invoice)) {
return $next($request);
}
return \response()->json(['message' => 'you are not authorized'], 401);
}
}
then, you can validate on the routes and you can use also policies and validate every feature alone
I'm new to laravel I have created middleware for my each role but when I add it to my route it won't work.
If I add single middleware to my route it works fine but when I add second and third one It will not work.
It won't shows the route to authorized user it redirect it to home,
My User Model:
public function IsAdmin()
{
if($this->role_id =='1')
{
return true;
}
else
{
return false;
}
}
public function IsManager()
{
if($this->role_id =='2')
{
return true;
}
else
{
return false;
}
}
public function IsUser()
{
if($this->role_id =='3')
{
return true;
}
else
{
return false;
}
}
My Kernal:
'IsAdmin' => \App\Http\Middleware\IsAdmin::class,
'IsManager' => \App\Http\Middleware\IsManager::class,
'IsUser' => \App\Http\Middleware\IsUser::class,
My IsAdmin Middlewares:
public function handle($request, Closure $next)
{
$user =Auth::User();
if(!$user->IsAdmin())
{
return redirect('stock');
}
return $next($request);
}
My IsManager
public function handle($request, Closure $next)
{
$user =Auth::User();
if(!$user->IsManager())
{
return redirect('stock');
}
return $next($request);
}
and IsUser
public function handle($request, Closure $next)
{
$user =Auth::User();
if(!$user->IsUser())
{
return redirect('stock');
}
return $next($request);
}
and finally my Route
Route::get('approv',['middleware'=>['IsManager','IsAdmin'],function(){
return view('approv');
}]);
This will not work as you'd expect. All middleware need to pass in order for the request to be processed which means that your user will need to be both a manager and an admin at the same time which based on your setup is impossible.
You can get around this (kind of) by making a different kind of middleware:
Kernel:
'roles' => \App\Http\Middleware\Roles::class,
And the Roles middleware:
class Roles {
private function checkRole($role) {
switch ($role) {
case 'user': return \Auth::user()->IsUser();
case 'manager': return \Auth::user()->IsManager();
case 'admin': return \Auth::user()->IsAdmin();
}
return false;
}
public function handle($request, Closure $next, ...$roles)
{
foreach ($roles as $role) {
if ($this->checkRole($role)) {
//At least one role passes
return $next($request);
}
}
//All checks failed so user does not have any of the required roles
return redirect('stock');
}
}
Then to use this you simply do:
Route::get('approv',['middleware'=>['roles:manager,admin'],function(){
return view('approv');
}]);
This works because Laravel Middleware support parameters. You can pass parameters as a comma separated list of strings where you declare the middleware. In this case this was done as roles:manager,admin
Laravel will then send these parameters as additional parameters in the handle method. These can be accessed using PHPs syntax for variadic arguments. In this particular case it's by using the array spread operator. This is documented as an example in the function arguments section of the PHP manual.
Note that this is actually equivalent to saying :
public function handle($request, Closure $next, $role1=null, $role2=null, $role3=null)
but using the spread operator is much more convenient since ...$roles would be an array which contains only the roles that were passed in the middleware.
I'm developing a Laravel ACL System. My base Table's are users,roles,permissions and pivot tables are role_user,role_permission,user_permission.
I want to check User Permissions using my custom middleware HasPermission. I have tried this way but it's not working properly. every user can access the all the permissions which have or have not.
Now, How can I solve the issue. Please see my code sample.
My Controller.
function __construct()
{
$this->middleware('auth');
$this->middleware('HasPermission:Role_Read|Role_Update|Role_Delete');
}
My Middleware.
class HasPermission
{
public function handle($request, Closure $next,$permissions)
{
$permissions_array = explode('|', $permissions);
// $user = $this->auth->user();
foreach($permissions_array as $permission){
if(!$request->user()->hasPermission($permission)){
return $next($request);
}
}
return redirect()->back();
}
}
and, my User Model method.
public function user_permissions()
{
return $this->belongsToMany(Permission::class,'user_permission');
}
public function hasPermission(string $permission)
{
if($this->user_permissions()->where('name', $permission)->first())
{
return true;
}
else
{
return false;
}
}
Best way to do is that you need to introduce an new service provider and in that you can check the authorization and permissions.
I made a test project (last year) for db driven permission and I used service provider.
That's the perfect way to implement.
Basically !$request->user()->hasPermission($permission) is saying if the user associated with the request does not have this permission the middleware passes, however this is not what you want. Here's what you should do:
If you need the user to have one of the stated permissions you need to do:
class HasPermission
{
public function handle($request, Closure $next,$permissions)
{
$permissions_array = explode('|', $permissions);
foreach($permissions_array as $permission){
if ($request->user()->hasPermission($permission)){
return $next($request);
}
}
return redirect()->back();
}
}
If you want the user to have all stated permissions you need to do:
class HasPermission
{
public function handle($request, Closure $next,$permissions)
{
$permissions_array = explode('|', $permissions);
foreach($permissions_array as $permission){
if (!$request->user()->hasPermission($permission)){
return redirect()->back();
}
}
return $next($request);
}
}
As an added note if you want to do this in a more elegant way you can do:
class HasPermission
{
public function handle($request, Closure $next, ...$permissions_array)
{
//Function body from above without the explode part
}
}
And
function __construct()
{
$this->middleware('auth');
$this->middleware('HasPermission:Role_Read,Role_Update,Role_Delete');
}
If you use commas then the framework will split the string into arguments for you .
In my case i just added simple function to get permissions from database and then check it Middleware. Check this code:
// Add new function to get permissions from database
public static function user_permissions($user) {
$permissions=DB::table('permissions')->where('user_id', $user)->first();
return $permissions;
}
// In Middleware check your permissions
if(Auth::guest())
{
return redirect('/');
}
elseif(Functions::user_permissions(Auth::user()->id)->user_managment != 1) {
return redirect('/');
} else {
return $next($request);
}
In web.php/api.php:
Route::middleware('hasPermission')->group(function() { // for all routes
Route::get('/article', [ArticleController::class, 'index'])->name('article.index');
});
in middleWare:
class HasPermission
{
public function handle($request, Closure $next)
{
$routeName = Request::route()->getName();
$permission = $user->permissions()->where('route_name', $routeName)->first();
if ( ! empty($permission)){
return redirect()->back();
}
return $next($request);
}
}
I created a simple system to set permissions for users in the admin panel using AdminMiddleware.
Every user has permissions like groups, posts, pages ....
Routes/web:
Route::group(['middleware' => 'admin'], function()
{
Route::get('/admin' , 'admin\AdminController#index')->name('admin');
//all admin panel routes
}
In the AdminMiddleware
public function handle($request, Closure $next)
{
if(Auth::check()){
if ($request->user()->user_type !== 'man')
{
return redirect('/');
}
$user_permissions = Auth::user()->permissions;
foreach($user_permissions as $value){
$controller = Controllers::get_controller($value->controller_id);
$permissions['name'] = $controller->controller_name;
$all_permissions[] = $permissions;
unset($permissions);
}
foreach ($all_permissions as $value){
$controllers[] = trim($value['name']);
}
$request->user()->controllers = $controllers;
return $next($request);
}
else{
return redirect('/login');
}
}
So I get a list of all user permissions which represent controllers names and in every controller (in this controller i check if the user has permission named 'users' to access the users controller to view their data)
protected $user;
public function __construct(){
$this->middleware(function ($request, $next) {
$this->user= Auth::user();
if(!in_array('users',$this->user->controllers)){
session()->flash('error' , 'No permission');
return redirect('/admin');
}
else{
return $next($request);
}
});
}
I use this for every controller and it works for a small project but when it comes to a large project with modules (nWidart/laravel-modules) it'll be hard. What I want is to check for the permission for the whole module not for every single controller in the module. So if I have a module named blog I want to check if the logged in user has permission to access any controller in that module how could this be done?
I created a middleware for every module and in the middleware i get user permissions and check if he the permission to access this group
public function handle($request, Closure $next)
{
if(Auth::check()){
if ($request->user()->user_type !== 'man')
{
return redirect('/');
}
$user_permissions = Auth::user()->permissions;
$user_group = Auth::user()->group_id;
if($user_group == 1){ //all permissions admin
return $next($request);
}
else{
//get user permissions as an array
if(in_array('groups',$user_permissions)){ //module name is groups
return $next($request);
}
else{
return redirect('/home');
}
}
}
else{
return redirect('/login');
}
}
Routes
Route::group(['middleware' => 'admin'], function()
{
Route::get('/admin' , 'admin\AdminController#index')->name('admin');
});
Route::group(['middleware' => 'users'], function()
{
Route::get('/adminUsers' , '\Modules\Users\Http\Controllers\UsersController#index');
});
Route::group(['middleware' => 'groups'], function()
{
Route::get('/groups' , '\Modules\Groups\Http\Controllers\GroupsController#index');
});
I have trouble redirecting after user authentication. I would like to redirect admin to admin panel, and user to home so I made admin middleware:
public function handle($request, Closure $next)
{
if (Auth::user() && Auth::user()->isAdmin()) {
return $next($request);
}
return redirect('/');
}
Routes for admin panel are:
Route::prefix('admin')->middleware(['web', 'admin', 'auth'])->group(function () {
Route::get('/', 'HomeController#index');
Route::resource('user', 'Admin\UserController');
});
I have User and Role models in a M-2-M relationship.
User model:
public function role(){
return $this->belongsToMany('App\Role');
}
public function isAdmin()
{
return ($this->role->first()->name == 'Admin') ? true : false;
}
Auth LoginController:
protected $redirectTo = '/admin';
Auth RedirectIfAuthenticated:
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/');
}
return $next($request);
}
Issue I'm having is that I always end up on home page. When watching through the inspector I noticed something strange, don't know if it is a standard procedure or not:
Login seems to be triggered twice? Route to /admin was triggered and got 200 OK status, but I never got to see it. If I manually enter it to the browser however, it will lead me to the admin dashboard.