Below description is based on Laravel development.
A brief description of the functionality I am hoping to achieve is,
There are 3 types of users. Superadmin, Admin and Enduser.
Only one Superadmin exist and he can create both Admins and Endusers. There can be multiple Admins exist and an admin is defined for a given site. An admin can create multiple Endusers.
To facilitate above use case, what sort of approach should I take in Laravel?
My attempt to accomplish this so far is:
I implemented multiple guards and then I was stuck since there are some routes which should be accessible by all types of users. I couldn't get it done with multiple guards since if I define multiple guards for a route, that route is only accessible only if all the multiple user types are logged in.
Say, I have a route guarded by Superadmin and Admin, this route is not available only if I logged in as Superadmin. Route is available only if both Superadmin and Admin are logged in.
Seems if we assign multiple guards, guards are ANDed. But I need them to be ORed.
guard assignment:
Route::group(['middleware' => ['auth:superadmin', 'auth:admin']], function() {...
Instead of Guards, I would separate out the SuperAdmin, Admin, and EndUser into individual middleware that performs a simple role check. For example a SuperAdmin middleware:
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::user()->isSuperAdmin) {
return $next($request);
}
abort(404);
}
then regular Admin
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::user()->isSuperAdmin || Auth::user()->isAdmin) {
return $next($request);
}
abort(404);
}
and then finally a simple check for authenticated users, i.e. EndUser
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()) {
return $next($request);
}
abort(404);
}
Then you can apply the middle to your groups as needed.
Route::group(['middleware' => ['superadmin']], function() {...
Route::group(['middleware' => ['admin']], function() {...
Route::group(['middleware' => ['enduser']], function() {...
By your logic, a Superadmin is always an admin, and an Admin is also an Enduser. If you start with opening routes in nested level you can get this work like.
Route::group(['middleware' => ['auth:enduser']], function(){
// All the routes available for Enduser
// For Admin
Route::group(['middleware' => ['auth:admin']], function(){
// Give admin routes here
//Create group for SuperAdmin
Route::group(['middleware'=>['auth:superadmin']], function(){
// Super admin routes
});
});
});
This way Superadmin had everything accessabile.
I managed to get it solved. No multiple guards. As #anwerj pointed out, all the users are type ENDUSER
Added user_type as an attribute to User model. SUPERADMIN, ADMIN and ENDUSER are the three user types. It is different from user role since a user can take multiple roles whereas once a user designated as ADMIN, will be ADMIN forever and he can have special privileges.
Implemented authorization mechanism where a route can be granted either
to a single user (i.e. only the granted user can have access to the particular route) or
to a user role (not the user_type mentioned above. A user role might have multiple users)
Routes were grouped to permission_set. A user_role can have multiple permission_sets
When a user logs in, middleware checks whether the resource being requested is granted for the User.
You can pass multiple arguments to a piece of middleware like this.
$this->group(['middleware' => ['restricted.role:super-admin,admin']], function () {
// ...
});
The RestrictedRole middleware class handle method will look like this.
public function handle($request, Closure $next, ...$roles)
{
if (Auth::user()->inRoles($roles)) {
return response()->json(['error' => "You don't have access to that"], 401);
}
return $next($request);
}
Finally, the User class will implement an inRole method like this.
public function inRoles($roles)
{
return in_array($this->getAttribute('role'), $roles);
}
You can also nest routes and restrict roles further like this.
$this->group(['middleware' => ['restricted.role:super-admin,admin']], function () {
// super-admin and admin can access this block of routes.
$this->group(['middleware' => ['restricted.role:super-admin']], function () {
// Only super-admin can access this block of routes.
});
});
Related
I am trying to implement a user registration system in Laravel 5.7 where I am facing an issue.
I have two tables for Users- Admin(created by copying default Laravel auth),
new routes, new middleware for admin. Every thing works fine while using guards.
I was trying to limit the user login by adding Approve/Disapprove functionality.
I added an extra column - admin(boolean) to the Users table.
In Login controller - LoginController.php Page, I added
protected function authenticated($request, $user)
{
if ( $request->user()->admin != 1)
// if($user->admin != 1)
{
return redirect()->route('approval');
}
else
{
return redirect('/engineer');
}
}
so that, when the admin is 1 I am directed to '/engineer' where as in other case I am directed to 'approval'.
It works as desired!.
Issue I am now facing is that if I try to access the 'engineer'
using user whose not approved I am able to access the page. I am not sure how to restrict it. The page is still restricted to public.
Since the controller will be accessed by both the user and admin, I used __construct in the controller
web.php
Route::resource('engineer', 'engineerController');
engineerController.php
public function __construct()
{
$this->middleware('auth:web,admin');
}
My Understanding is that the condition is only checked when the user logs in and there after its exits.
Do I need to create a new middle ware in order to keep the authorised page intact?
I am a self learner and new to laravel. I am pretty sure that I am not following the right practice. I started something and was trying to follow it till I finish. Please guide me through it.
Along with it please let me how could I have done it better.
You would need to define a Middleware that would check if the Engineer is approved or not.
Obviously, you would also need to keep that in an is_approved column for example.
<?php
namespace App\Http\Middleware;
use Closure;
class CheckEngineerApproval
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! auth()->user->is_approved) {
return redirect('user.approve');
}
return $next($request);
}
}
Then, add it in your $routeMiddleware array in your Kernel.
protected $routeMiddleware = [
//
//
'engineer.approved' => \App\Http\Middleware\CheckEngineerApproval::class,
];
Finally, you can add the Middleware in your Controller's constructor as well.
public function __construct()
{
$this->middleware(['auth:web','admin','engineer.approved']);
}
I have a middleware group of auth inside that i want to apply another middleware to one specific route in that view that is if the profile is not completed user cant go to any other route until he complete his profile and submit.
More Specifically, middle ware is causing loop on redirect because i have 2 middleware.
i created middleware with laravel php artisan and checking the user if profile is incomplete he should redirect to the profile/edit page but its not even working with just check on incomplete empty companyname.
Middlware
class incompleteProfile
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(empty(Auth::user()->details['companyname'])){
return redirect()->route('profile');
}
return $next($request);
}
}
Routes File
Routes
Route::group(['middleware'=>['auth'] ], function(){
// User Profile
Route::get('/profile/edit', 'UserController#profile')->name('profile')->middleware('incompleteProfile');
Route::post('/profile/edit', 'UserController#editProfile')->name('editProfile');
If you put that middleware on the profile route ... how can they ever get to the profile route to get the form to update the profile to add the missing information?
You are saying ... if user details for company name are empty, then redirect to profile, but you have that middleware on profile ... so its going to forever redirect to profile because the middleware is telling it to. Your user can never get to profile in this case.
This is the equivalent of assigning the auth middleware to the login page. The auth middleware checks if the user is currently authenticated. Which means if a user is not authenticated they would never be able to get to login, in this scenario, as login requires them to be "logged in".
lagbox answer pretty much says the logic of why it doesn't work.
Try it like this.
Route::group(['middleware'=>['auth'] ], function(){
// User Profile
Route::get('/profile/edit', 'UserController#profile')->name('profile');
Route::group(['middleware'=>['incompleteProfile'] ], function(){
Route::post('/profile/edit', 'UserController#editProfile')->name('editProfile');
//ETC
});
});
I have an admins table that has some columns like id, admin_type, name, etc. Here I have two types of admin_type: one is "admin" and the other is "hod".
Now the problem is that I want admins that login with (admin_type == "admin") to be able to access all the admin URLs, but when admin_type == "hod" I want to limit the URLs the user can access.
I am using Laravel 5.2. Can anyone help me to resolve this issue?
For example, if a user with admin_type=="hod" accesses these links
www.example.com/admin/add-user
www.example.com/admin/edit-user
www.example.com/admin/add-customer
www.example.com/admin/edit-customer
and many more URLs, I want to show some message like **You have no rights to access this link **
Here is my database structure:
I would implemented a Middleware for such a use case. Just execute php artisan make:middleware yourNameHere in your Laravel working directory and artisan generates the corresponding middleware class for you.
Then you need code like this. It's a simple if condition with an 403 abort in case that the user is no admin.
class AdminMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->user()->admin_type != 'admin') {
return abort(403, "No access here, sorry!");
}
return $next($request);
}
}
Your routes file (routes/web.php):
...
Route::group(["middleware" => 'admin'], function () {
//Your admin routes should be declared here
});
...
I am new to Laravel, and I am trying to authenticate a user from players table.
As we know Auth::attempt is used to authenticate a user, and by default it works for users table, and for users table it is working perfect. But now I want to authenticate another user (a player) from another table (players), but I am unable to find a solution.
I had the same requirement. I had two user tables users(for main website) and admins (for admin panel). For that to happen, I must authenicate admin user against admins table. I wanted to use stock authentication library. So, I made following middleware.
Of course I had separate login form for admin panel
<?php namespace App\Http\Middleware;
use Closure;
class ChangeUserToAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
\Config::set('auth.table', 'admins');
\Config::set('auth.model', 'App\DB\Admin\Admin');
\Config::set('session.cookie', 'admin_session');
\Config::set('session.path', '/admin/');
return $next($request);
}
}
All my routes within admin route group (i.e. domain.com/admin/**) were protected by this middleware. So essentially I changed authentication model and table for admin area.
I'm trying to use the Laravel auth out of the box. The authentication is not the problem but I want to check if the user has confirmed his email address.
How I let have Laravel check if the table value confirmed has the value 1.
In config/auth.php I have set 'driver' => 'database' so if I understand the docs right I can then do manual authentication and I guess I can then check if the user has confirmed his account.
Where does Laravel perform the check for a matching username and password?
If you're using Laravel Auth out of the box, you want to go take a look at the AuthController that has been setup for you.
You'll see that this uses a trait AuthenticatesAndRegistersUsers to add behavior to the controller.
Inside that trait you'll find the method postLogin.
You will want to override this method, by adding your own postLogin to the AuthController. You can copy and paste the method for starters.
Now go take a look at the Laravel docs on authentication. Scroll down to where it talks about "Authenticating A User With Conditions."
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1]))
{
// The user is active, not suspended, and exists.
}
Change the attempt() code in your postLogin method to include the condition, as shown in the example. In your case you would probably want to pass in a condition of 'confirmed' => 1 instead of active, depending on what you call the field in your users table.
That should get you going!
Create a middleware class:
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class UserIsConfirmed {
/**
* Create the middleware.
*
* #param \Illuminate\Contracts\Auth\Guard $auth
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->user()->isConfirmed())
{
// User is confirmed
}
else
{
// User is not confirmed
}
return $next($request);
}
}
I don’t know what you want to do in the case a user is or is not confirmed, so I’ll leave the implementation up to you.