Where to apply custom middleware for authentication Laravel 5.2 - laravel

In my project two type of users, Admin and Normal User. They are identified by the field isAdmin in users table. User can edit their profile by using the function below
public function userEditprofile(){
$user_detail = userDetail::find(Auth::user()->id);
$user_detail->address = Input::get('address');
.......
$user_detail->save()
return Redirect::route('showUserProfile');
}
and route is
Route::group(['middleware' => 'my_profile'], function() {
Route::get('/editprofile', array('uses' => 'UserController#userEditprofile', 'as' => 'userEditprofile'));
});
Admin can also edit any users profile by using
public function adminEditUserprofile($user_id){
$user_detail = userDetail::find($user_id);
$user_detail->address = Input::get('address');
.......
$user_detail->save()
return Redirect::route('showUserProfile', $user_id);
}
In both cases action is same but in first method there is no parameter is required. But in the case of admin , a parameter is required. Can I optimize the code by using any other way? I am a self learned programmer. I am not much aware of efficient programming methods.Can anyone reply?

I believe you should rather implement role based authorization. A good example has already been implemented by rappasoft/laravel-5-boilerplate
You should actually prefer this boilerplate as it includes a lot that is necessary for most of business applications.

Related

Why InitializeTenancyByDomain is not applicable for the login process?

This is for Laravel 8.x with Jetstream/Livewire scaffold having Stancl/Tenancy. The initialization of tenant models or session settings not working right. Either I did not do it right or inbuilt problem.
The entire package was built as per instructions of Stencl/tenancy v3.x. I can see the dd(\App\User::all()) as per code outlined below
Route::middleware([
'web',
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
])->group(function (){
Route::get('/', function () {
dd(\App\User::all()); //can see all users models in tenants table
return view('welcomeTenant');
});
Route::get('/home', [
'middleware' => ['auth'],
'uses' => '\App\Http\Controllers\HomeController#index'
])->name('home');
});
This meant InitializeTenancyByDomain right to me.
When a login form is requested from tenant's domain eg. from rtbs.example.in, the encrypted session/cookie info is not stored in sessions table of tenant i.e. rtbs.sessions. When a login form is posted, it is looking for users in central domain (example.in) where users table is not present, hence the central.users table not exist error. As a result I get 419 error. I had disabled the csrf token verification temporarily to identify this problem.
This is the issue. Why the InitializeTenancyByDomain is not applicable for the login process? Could there be a fundamental setting wrong with me? Interestingly, the dd(\App\User::all()) if present anywhere else i.e. as show below
Route::middleware([
'web',
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
])->group(function (){
dd(\App\User::all()); //central-domain.users does not exist error
Route::get('/', function () {
return view('welcomeTenant');
});
Route::get('/home', [
'middleware' => ['auth'],
'uses' => '\App\Http\Controllers\HomeController#index'
])->name('home');
});
The same sql exception i.e. central-domain.users table does not exist is thrown. Only when present inside the Route::get('/'... i can see the correct models.
I had this problem with Laravel Fortify, which provides the backend to JetStream. It only became a problem when I changed the session driver to database from file - though this will be necessary for my deployment anyway.
The issue is that Fortify makes extensive use of dependency injection inside the constructor methods of their controllers. As described in the documentation for Tenancy, because of the lifecycle of a Laravel request the tenancy will not be available when these constructors run. This means they will default to the central connection and cause failure to login.
Because we can't change the way Fortify uses constructors, the solution is to change the middleware to be global and add a check for central domains into the initialization middleware:
Copy Stancl\Tenancy\Middleware\InitializeTenancyByDomain to App\Middleware\InitializeTenancyByDomain changing:
public function handle($request, Closure $next)
{
//Skip for central domains
if(in_array($request->getHost(), config('tenancy.central_domains'), true)){
return $next($request);
}
return $this->initializeTenancy(
$request, $next, $request->getHost()
);
}
App/Http/Kernel
use App\Http\Middleware\InitializeTenancyByDomain;
protected $middleware = [
// ...
InitializeTenancyByDomain::class,
];
Config/fortify
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;
'middleware' => [
'web',
PreventAccessFromCentralDomains::class,
],
Routes/tenant
Route::middleware([
'web',
PreventAccessFromCentralDomains::class,
])->group(function () {
//Your Routes
}
With this solution Fortify and Stancl Tenancy are now working well together with well separated databases, sesssions and logins.
I got it to work with Rory's answer. However, you need to set the correct namespace in App\Http\Middleware\InitializeTenancyByDomain.php:
namespace App\Http\Middleware; // << use this instead of Stancl\Tenancy\Middleware
// [..]
use Stancl\Tenancy\Middleware\IdentificationMiddleware; // <<
class InitializeTenancyByDomain extends IdentificationMiddleware
That package is very helpful, but it requires a lot of work to get all the pieces lined up and maintained. You've got the right middleware, including 'web' and the two tenancy bits, which will start the session and allow checks for CSRF.
I've had similar problems before. The biggest cause for me was that it was not finding the right 'central' path, and thus not initializing tenancy correctly.
Make sure your tenancy.php config file is showing the right central domain. Something like:
'central_domains' => [
'example.in' // No http:// here
],
Route's like next can issue bugs with database session store
Route::view('some')
You could use a feature called Universal Routes check the documentation here

Create generic route-based authorization in Laravel

I'm coming from conventional PHP background and trying to create my first big project in Laravel.
I usually user User/Role/Permission to manage user permissions in my applications. It works like follows:
User has many Roles
Role has many Permissions
to make things simple, I actually used the page names as permissions, so that I check the current page name against user permissions.
That was all easy in PHP, now I am trying to implement a similar approach in Laravel. I have User, Role, Permission models, and I check if user has permission using a method in User model as follows (inspired from a Laracasts tutorial):
public function permissions()
{
return $this->roles->map->permissions->flatten()->pluck('name')->unique();
}
And in my AuthServiceProvider I added the following code:
Gate::before(function ($user, $permission){
return $user->permissions()->contains($permission);
});
So if I add some permission (for example 'add_user') to the user, I can simply do the following in the route, and it works just fine:
Route::get('/test', function () {
return 'You are authorized';
})->name('add_user')->middleware('can:add_user');
Now since I have a lot of pages, I wouldn't like to pass specific permission name to the middleware, rather find a better and more generic way.
The only way I could come up with is to use the permission name same as the route name, and create a new middleware to take care of authorization.
So In my solution I added the following middleware class:
class BeforeMiddleware
{
public function handle($request, Closure $next)
{
$route_name = $request->route()->getName();
if(!Auth::user()->permissions()->contains($route_name)) {
throw new \Exception('Not Authorized');
}
return $next($request);
}
}
Added it to Kernel.php:
protected $routeMiddleware = [
'before' => \App\Http\Middleware\BeforeMiddleware::class,
...
];
And finally changed the route to be as follows:
Route::middleware(['before'])->group(function () {
Route::get('/test', function () {
return 'You are authorized';
})->name('add_user');
});
This way I don't actually have to pass the permission name when I check the permission, and directly get it from the route name.
I have many questions about my solution: is it really a good approach? Does it have any drawbacks? Is there a better approach?
Also I preferred to use AuthServiceProvider instead of the new middleware, but I couldn't retrieve the route name from ServiceProvider scope. Can I somehow use AuthServiceProvider for a similar case?
Sorry if I made the post somehow long, but I needed to be as clear as I could.

How to create single endpoint for authenticated or non-authenticated User in laravel?

I need a single endpoint where I can check if User authenticated then can return some user-related data else(if not-authenticated) some basic information that I want.
I tried by checking user having a token or not in the api.php file. but it is not working.
The example below is basically generated by following the official documentation.
//file: ./routes/web.php
use Illuminate\Support\Facades\Auth;
Route::get('profile', function () {
if (Auth::check()) {
return Auth::user();
} else {
return ['foo' => 'bar'];
}
});
Have in mind that this is extremely simplified example.
In a real world case use probably you would have a route pointing to a controller method where you can auto-inject the Illuminate\Auth\AuthManager.
Also you wouldn't be returning the whole user object, but rather transforming the response to your needs.

Route User Role Laravel 5.4

I'm very confused on this situation. I have two routes with on resource name.
Route::resource('product', 'Product\AreaManagerProductController');
Route::resource('product', 'Product\SystemAdminProductController');
I need to make it as one because I have a contextual binding.
$this->app->when(AreaManagerProductController::class)
->needs(ProductInterface::class)
->give(AreaManagerProductRepository::class);
$this->app->when(SystemAdminProductController::class)
->needs(ProductInterface::class)
->give(SystemAdminProductRepository::class);
The contextual binding works fine... but I need to change my route like this.
Route::resource('product_area_manager', 'Product\AreaManagerProductController');
Route::resource('product_system_admin', 'Product\SystemAdminProductController');
I created ProductController and some kind of weird solution.
public function index(){
//Create a conditional statement base on user
return app('App\Http\Controllers\Product\AreaManagerProductController')->index();
}
It may work but it doesn't trigger the middleware... What could be the best practice on this situation. TY
You can have your Route like this
Route::group(['prefix' => 'product', 'namespace' => 'Product', 'middleware' => '<your middleware>'], function() {
Route::resource('area_manager', 'AreaManagerController');
Route::resource('system_admin', 'SystemAdminController');
});
The reason I grouped the route is to reduce redundancy, and the reason i removed Product from the controller name is, as there is a namespace Product already, there is no need of long Class names.
If you wan to access some methods in the AreaManagerController and SystemAdminController just extend the ProductController to these Controllers.
If you want to add some specific middleware for the actions inside these controllers, I have added a middleware clause in the route group which will affect to these controllers, if not needed just remove it.
Hope this helps you.

two users auth in a single table in laravel 5.2

I'm trying to build an online shop website using laravel framework
I have a table with these column : id, username, password, division_id
where division_id refers to a table called division with: id, division_type
and I have two division type : Admin and Customers
I'm trying to divide permission to access a certain number of pages based on their division_id such as admin can access Admin panel but not Customer, and customer can access Customer panel such as product-order page but not admin.
Both types can do almost everything in their accessible pages, and my main pages will have an Admin Panel, a customer Panel, and main website.
How can I do that in my project using only a single table and middle-ware group?
P.S. I'm new to this forum
For this Middleware, you just need to check if the division required to view the site is the same as the division that the user belongs to. In the handle function, you can pass a 3rd argument that represents a division name, such as customer
When you add the Middleware to your routes, you can pass the name of the division as an argument to the handle function like so:
'middleware' => ['division:customer']
Implementing this in an Route Group may look something like this:
Route::group(['prefix' => 'customer', 'middleware' => ['division:customer']], funtion(){
//route definitions for all these routes will require a "division" type of "customer"
});
Or you could apply it to route resources for RESTful routing:
Route::resource('customer', 'CustomerController')->middleware(['divison:customer']);
Or you could just apply it to a specific route:
Route::get('customer/{id}', 'CustomerController#show')->middleware(['division:customer']);
In your handle function you can access that value as the 3rd argument:
public function handle($request, Closure $next, Division $division)
To make the process of automagically resolving a dependency by something other than the primary key easy, we'll go ahead and pop open our App\Providers\RouteServiceProvider and add some magic inside of the boot function.
public function boot(Router $router)
{
parent::boot($router);
$router->bind('division', function($value) {
return Division::where(function($query) use($value){
if (is_int($value)) {
return $query->where('id', $value)->first();
} else {
return $query->where('type', ucfirst($value))->first();
}
return null;
});
});
Now, back to the Middleware, we can easily make a comparison against the $division in our handle function, and our authorized user.
if(app()->user()->division->type == $division->type) {
return $next($request);
}
abort(403, 'You are not authorized to view this page!');

Resources