Can't protect Routes based on role Laravel 8 - laravel

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 () {

Related

How can I change the filed email from Larval 8 auth user table to username?

currently, I'm trying to change the field email in user table to username. But seems like it did not work at all. What I did just replace the email (inside of $fillable array) to username.
2.Here is the code for User.php file.
<?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 Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name',
'username',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* #return mixed
*/
public function getJWTIdentifier() {
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* #return array
*/
public function getJWTCustomClaims() {
return [];
}
public function username()
{
return 'username';
}
}
Here is the schema for my user table
You also have to update your user table migration. If you don't need the email field, you can replace the line :
$table->string('email')->unique();
with :
$table->string('username');
Then, run :
php artisan migrate:refresh
For this situtation you need to update your migration file for this, then you can use your username without e-mail.

retrive relation table in login request with laravel breeze

Hello I want to include my relation table roles when I login. I used Laravel Breeze for authentication.
Models/User.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 Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
//old
public function bookmarks(){
return $this->hasMany('App\Model\Post','post_bookmarks','user_id','post_id')->get();
}
public function roles(){
return $this->belongsTo('App\Models\Roles','roles','role_id','id');
}
}
AuthenticatedSessionController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*
* #return \Illuminate\View\View
*/
public function create()
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*
* #param \App\Http\Requests\Auth\LoginRequest $request
* #return \Illuminate\Http\RedirectResponse
*/
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
/**
* Destroy an authenticated session.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request)
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
LoginRequest.php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
public function authenticate()
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
/**
* Ensure the login request is not rate limited.
*
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited()
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
/**
* Get the rate limiting throttle key for the request.
*
* #return string
*/
public function throttleKey()
{
return Str::lower($this->input('email')).'|'.$this->ip();
}
}
I have used Laravel-breeze for the authentication. It's all working fine but I want to add Role name also when user gets login. As you can see I have also used relationship but I'm confused where can I write the code to get my relation table. This is my code, Can anyone please tell me how can I do?
For Ex.
login email: poojan#gmail.com
role_id = 1
in roles table
id: 1, role_name = "Wholseler"
So when I logged in currently I'm getting user data, But I want roles table data too.
Thanks in Advance
you can use attribute in your user model
protected $appends = ['role_name'];
public function getRoleNameAttribute()
{
return $this->roles->first()->role_name ?? 'N/A';
}
using the above code you will get the role_name attribute in the user instance
if you want roles data into relations of user data try this in user model
protected $with = ['roles'];

Laravel Relationship Null

I am having an issue following a tutorial on YouTube about relationships.
I have replicated this code from the tutorial and I keep getting errors.
I've tried changing the controller code from auth() to app etc.
Also, I've tried re-running migrations:fresh etc and nothing.
User Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Notifiable, Billable;
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Get the Instance associated with the user.
*
* #return HasMany
*/
public function instance()
{
return $this->hasMany(Instance::class);
}
}
Instance Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Instance extends Model
{
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name'
];
public function user()
{
return $this->belongsTo(User::class);
}
}
Controller
<?php
namespace App\Http\Controllers;
class SyncController extends Controller
{
public function successful()
{
return auth()->user()->instance()->create(['name' => 'test']);
}
}
Error
Call to a member function instance() on null {"exception":"[object] (Error(code: 0): Call to a member function instance() on null at /home/#/cc.#.io/app/Http/Controllers/SyncController.php:14)
[stacktrace]
Edit:
Route::middleware(['auth'])->group(function() {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
Route::get('/subscribe', SyncController::class);
});
Check if your route is guarded by auth middleware. If not you can add that in order to fix. You might use Route group like following -
Route::group(['middleware' => ['auth']], function () {
Route::resource('your_url', 'YourController');
// Or whatever route you want to add...
});
This is because the auth()->user is getting null and it will be necessary to check if the value was actually received after the call was made.

Call to undefined method App\Models\User::hasRole() with a middleware Laravel 8

I'm trying to use a middleware to validate the roles and I'm using the example that is in Laravel documentation.
<?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);
}
}
But I'm getting the error of the question. This is how I have written the Route.
Route::get('/gestionarMedicos', [PersonaController::class,'mostrarMedicos'])->name('personaMostrarMedicos')->middleware('auth','firstLogin','role:administrador');
The middleware is called role and I plan to use it to only allow access the supported roles for it.
It surprises me it doesn't work because I'm using the example in Laravel documentation.
The User model code
<?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',
];
}
Add EnsureUserHasRole to your middleware array
Route::get('/gestionarMedicos', [PersonaController::class,'mostrarMedicos'])->name('personaMostrarMedicos')->middleware('auth','firstLogin','EnsureUserHasRole', 'role:administrador');

403 user does not have any necessary access rights in laravel usercontroller laratrust

I am trying to get all user in my table but I get an error 403 user does not have the necessary rights. I am using laratrust and vuejs. I have already logged in as a superadministrator. This is my controller class
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\user;
use Illuminate\Support\Facades\Hash;
class UserController extends Controller
{
public function __construct()
{
$this->middleware('role:user|superadministrator');
}
public function index()
{
return view('user.index');
}
public function getusers(){
$theUser = Auth::user();
if ($theUser->hasRole('superadministrator')) {
return $users;
}
}
}
My api route
Route::get('/allusers','UserController#getusers');
I have tried to go through the documentation but no success.Kindly help me solve this issue
User Model
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laratrust\Traits\LaratrustUserTrait;
class User extends Authenticatable
{
use LaratrustUserTrait;
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
What about if you try this:
public function getusers(Request $request){
$theUser = $request->user('api');
if ($theUser->hasRole('superadministrator')) {
return $users;
}
}

Resources