I have a Laravel project where I'm using middleware to create users roles like admin/moderator, and I want to restrict access to routes only. I have users table with a role column where my user role is 'admin'. When I go to /posts page in web.php I should be able to access it since I'm admin, but for some reason I get 404 not found. Can someone please help me?
web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('types', function () {
Route::get('/posts',[\App\Http\Controllers\ProductController::class,'posts']);
})->middleware('roles:admin');
app/Http/Controllers/ProductController.php
public function posts(){
$product = Product::orderBy('created_at', 'desc')->paginate(21);
return view('posts', ['products' => $product]);
}
app/Http/Middleware/UserRoles.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class UserRoles
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, ...$roles)
{
return collect($roles)->contains(auth()->user()->roles) ? $next($request) : back();
}
}
Kernel.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use App\Http\Middleware\UserRoles;
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'roles' => UserRoles::class,
];
}
You has a problem in web.php - get in get is not allowed. Use Route::group or Route::middleware
Route::middleware('roles:admin', function () {
Route::get('/posts',[\App\Http\Controllers\ProductController::class,'posts']);
});
Related
I am not sure how to fix this. When I log in as a user, it redirects to my user dashboard. If I try to change the URL from /user/dashboard to /admin/dashboard, it gives an error saying no admin rights. Perfect, but when I login as admin it does go to the admin dashboard. When I change the URL from /admin/dashboard to /user/dashboard, the /user/dashboard is not protected and it goes to it.
AdminMiddleWare
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AdminMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
if (Auth::user()->role_as == '1'){
return $next($request);
} else {
return redirect('login')->with('status','You Do Not Have Admin Rights');
}
}
}
LoginController
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
// protected $redirectTo = RouteServiceProvider::HOME;
protected function authenticated()
{
if(Auth::user()->role_as == '1') {
return redirect()->route('admin.dashboard')->with('status','Welcome to your Admin Dashboard');
} else {
return redirect('/home')->with('status','Welcome To Your User Dashboard');
}
}
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
Routes
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])
->name('user.dashboard');
Route::get('/dashboard', [AdminController::class, 'index'])
->middleware('isAdmin')
->name('admin.dashboard');
In my users table I have a field (boolean):
active
My routes currently use
Route::middleware(['auth'])->group(function ()
but I want to do is to add the active so they will have to be authorised and active.
I can't seem to find the answer.
Thanks in advance!
Add another middleware to group.
Route::middleware(['auth','active'])->group(function ()
You can implement that middleware like
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Active
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
if (!$request->user()->active){
return redirect('login');
}
return $next($request);
}
}
Obviously you need to register this middleware into app/Http/Kernel.php
protected $routeMiddleware = [
....
'active' => \Illuminate\Auth\Middleware\Active::class,
]
I hope this help
I'm trying to use middlewares to protect Routes based on role.
Im testing with this route to allow only the role administrador to be able to enter it.
Route::get('/gestionarMedicos', [PersonaController::class,'mostrarMedicos'])->name('personaMostrarMedicos')->middleware('auth','firstLogin','role:administrador');
This is code of my middleware (in the route its the one called route)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureUserHasRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next, $role)
{
if (! $request->user()->hasRole($role)) {
// Redirect...
return back();
}
return $next($request);
}
}
This is the code of the User model
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'role',
'name',
'email',
'password',
'idPersona',
'estado'
];
public function Persona(){
return $this->belongsTo(Persona::class,'idPersona');
}
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'remember_token'
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function hasRole($role)
{
return User::where('role', $role)->get();
}
}
Some logic is not done right because I can access the Route even with users that doesn't have that role
The dd() in the middleware was for testing, I get "administrador" with it.
I have tried Patrick Obafemi solution but I still have the same problem.
For testing I did a dd of what the if based on Patrcik answer result is and it is false.
I'm not sure where is the logic problem.
I'm also going to post a picture of my database model if it helps in some way.
The question is answered in the link below.
Middleware doesn't protect routes based on role
It also covers how to protect routes in the case you need to do it for multiple roles. The condition is wrong because it gives a collection of the users that have the role administrador. The condition should be like this to only allow a desired role to access the Route
if (!$request->user() || $request->user()->role != $role) {
// Redirect...
return back();
}
For multiple roles you can visit the link where the answer explains how to allow multiple desired roles to access the Route.
Maybe Patrick answer is right but question was also answered here.
I think your error is coming from your hasRole in your User Model. The result user() already has access to your user model so you can just make it a local scope. What you are doing now in your middleware will return something like this. So using the get function will return a collection of users but to check if that user exists you should use first() or better still exists(). Also you cannot call where on a single model entity and that's what $request->user()->role() is trying to do
User::User::where('role', $role)->get();
Try this instead
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureUserHasRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next, $role)
{
$id = $request->user()->id;
if (!User::where([['id', $id],['role',$role]])->exists()) {
// Redirect...
return back();
}
return $next($request);
}
}
Then your model will look like this
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Model;
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'role',
'name',
'email',
'password',
'idPersona',
'estado'
];
public function Persona(){
return $this->belongsTo(Persona::class,'idPersona');
}
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'remember_token'
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function scopeRole($query, $role)
{
return $query->where('role', $role);
}
}
even your class diagram has a problem. if there are roles then there should be an inheritance. already how do you materialize this here. in short the solution is to make a gates or a policies or a middleware. i have an example of middleware but just for the verification if a user is admin.
class VerifyIsAdmin
{
public function handle($request, Closure $next)
{
$user = $request->user();
if ($user && $user->role === 'admin') {
return $next($request);
}
return abort(403);
}
}
this concerns the verifyIsAdmin middleware. then you go to
APP/Http/Kernel
then paste
protected $routeMiddleware = [
'admin' => VerifyIsAdmin::class,
];
and add the following line in the namespace
use App\Http\Middleware\VerifyIsAdmin;
and add to the route group
Route::middleware(['admin'])->group(function () {
I have ReCaptcha in Register controller and I wanted to put it in the login controller here like
<?php
namespace App\Http\Middleware;
use App\Rules\Captcha;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use App\Rules\Captcha;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
protected function validateLogin(Request $request)
{
$this->validate($request, [
'g-recaptcha-response' => new Captcha(),
]);
}
}
But Im getting an error Cannot use App\Rules\Captcha as Captcha because the name is already in use
Is there other ways to put ReCaptcha in the reg and log?
You have the following line twice at the start of your file:
use App\Rules\Captcha;
In my application I have three user roles:
user
editor
admin
When editor logs into the admin section, some of the sections are hidden (users manage, system information etc.) and of course, the admin can see everything.
So, for this purpose I've created two middleware: Admin and Editor. This is the code.
Admin middleware.
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class Admin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::check()) {
if(Auth::user()->role_id == 3) {
return $next($request);
}
}
return redirect('/');
}
}
Editor middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class Editor
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::check()) {
if(Auth::user()->role_id == 2) {
return $next($request);
}
}
return redirect('/');
}
}
Here's the part of the Kernel:
protected $routeMiddleware = [
'admin' => \App\Http\Middleware\Admin::class,
'editor' => \App\Http\Middleware\Editor::class,
];
Now I'm trying to build the routes that will be available to those user roles.
If I do it only for the admin or the editor, it works fine, but when I combine them, the one user can login and the other cannot.
Here's the code only for the admin and it works fine.
Route::middleware('admin')->group(function(){
Route::get('/admin', 'PagesController#adminIndex');
Route::resource('/admin/pages', 'PagesController');
Route::resource('/admin/news', 'NewsController');
Route::resource('/admin/users', 'UsersController');
...
});
I've tried to combine them with this code, but it's not working (cannot login into the admin section at all):
Route::middleware(['admin', 'editor'])->group(function(){
Route::get('/admin', 'PagesController#adminIndex');
Route::resource('/admin/pages', 'PagesController');
Route::resource('/admin/news', 'NewsController');
Route::resource('/admin/users', 'UsersController');
...
});
How can I solve this problem?
P.S. Later I want to build a logic for the User role too, so there's must a way to combine the routes.
You can solve the problem with help of Middleware Parameters and instead of several middlewares for each role use only one universal middleware with roles as parameters.
For example:
protected $routeMiddleware = [
'checkRole' => \App\Http\Middleware\CheckRole::class,
];
Middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class CheckRole
{
public function handle($request, Closure $next, ...$roles)
{
$roleIds = ['user' => 1, 'editor' => 2, 'admin' => 3];
$allowedRoleIds = [];
foreach ($roles as $role)
{
if(isset($roleIds[$role]))
{
$allowedRoleIds[] = $roleIds[$role];
}
}
$allowedRoleIds = array_unique($allowedRoleIds);
if(Auth::check()) {
if(in_array(Auth::user()->role_id, $allowedRoleIds)) {
return $next($request);
}
}
return redirect('/');
}
}
Routes:
Route::middleware(['checkRole:admin,editor'])->group(function(){
//Your routes
});
It should be like below.
Route::middleware(['auth'])->group(function(){
//common routes will goes here
Route::middleware(['admin'])->group(function(){//admin routes will goes here
Route::get('/admin', 'PagesController#adminIndex');
Route::resource('/admin/pages', 'PagesController');
Route::resource('/admin/news', 'NewsController');
Route::resource('/admin/users', 'UsersController');
});
Route::middleware(['editor'])->group(function(){
//editor routes goes here.
});
});
The problem is that your middleware(['admin', 'editor']) is checking the both roles i.e. admin,editor for user and you have only one role for user. That is reason why it is not working
There are great packages out there for managing the user roles which are easy to use . I suggest you to use Spatie Laravel Permission if you want tutorials on it watch Bitfumes Video