Laravel passport - Allow user to act as/login as other user - laravel

I'm working on an application where some users should have access to other user accounts. For example: In a family, the mother and all 3 kids have an account. Now the mother should have access to all of the kids accounts.
Is there a possibility to setup something like this in Laravel using Passport? I thought about a "permission" database table with two columns (parent_account, child_account). Parent accounts could then switch between accounts where they have the permission.
Perfect would be something like a middleware where I can set Auth::actAs($child);and after that every Auth::user() call would be the child until I switch back to the "normal" account.
Additional information: I'm using Laravel to provide an API for my React Frontend Application. I tried the Auth::loginUsingId function, but when I use it I get logged out and I get the Method Illuminate\Auth\RequestGuard::loginUsingId does not exist. Exception.
I am using Laravel Version 6.9.0

I found a solution to my problem.
I added a middleware that contains this piece of code:
public function handle($request, Closure $next)
{
$activeChild = Auth::user()->activeChild; // id of child user
if ($activeChild) {
Auth::setUser($activeChild);
}
return $next($request);
}
After that I added this middleware to all routes:
Route::group(['middleware' => ['actAsUser']], function () {
// some routes
});

Related

Laravel - Controller dependency is injected before middleware is executed

So I created a middleware to limit the data a connected user has access to by adding global scopes depending on some informations:
public function handle(Request $request, Closure $next)
{
if (auth()->user()?->organization_id) {
User::addGlobalScope(new OrganizationScope(auth()->user()->organization));
}
return $next($request);
}
The middleware is added to the 'auth.group' middleware group in Kernel.php which is used in web.php:
Route::middleware(['auth.group'])->group(function () {
Route::resource('users', UserController::class);
});
Then in the controller, I would expect a user to get a 404 when trying to see a page of a user he has no rights to. But the $user is retrieved before the middleware applies the global scope!
public function show(User $user, Request $request) {
// dd($user); // <= This actually contains the User model! It shouldn't, of course.
// dd(User::find($user->id)); // <= null, as it should!
}
So, the dependency is apparently calculated before the middleware is applied. If I'm trying to move the middleware into the 'web' group in Kernel.php it's the same. And in the main $middleware array, the authenticated user's data is not available yet.
I found this discussion that seems to be on topic : https://github.com/laravel/framework/issues/44177 but the possible solutions (and Taylor's PR) seems to point to a solution in the controller itself. Not what I'm trying to do, or I can't see how to adapt it.
Before that I was applying the global scopes at the Model level, in the booted function (as shown in the docs). But I had lots of issues with that - namely, accessing a relationship from there to check what is allowed or not is problematic, as the relationship call will look for something in the Model itself, and said model is not ready (that's the point of the booted method, right...). For example, checking a relationship of the connected user on the User model has to be done with a direct query to the db, that will be ran every time the Model is called... Not good.
Anyway, I like the middleware approach as it is a clean way to deal with rights as well, I think. Any recommandation?
Not a recommendation, just my opinion.
This issue is just because of that Laravel allow you add middleware in controller constructor, and that's why it calculate before midddleware in your case.
I agree that middleware is a clean way to deal with auth, but i also think that you are not completely doing auth in your middleware, for example if you create a new route will you need to add something auth action into your new controller or just add auth middleware to route?
If does needs add something to controller, that means your auth middleware is just put some permissions info into global scope and you are doing the auth in controller which i think it's not right.
Controller should be only control the view logic, and you should do full auth in your auth middleware, once the request passed into your controller function that means user passed your auth.
For some example, if you auth permissions like below, you can just add auth middleware to new route without any action in your controller when you trying to create new route.
public function handle(Request $request, Closure $next)
{
if (auth()->user()->canView($request->route())) { // you should do full auth, not just add informations.
return $next($request);
}
else
abort(404);
}

How to authenticate a user by its user id in Laravel in a custom middleware

How would you guys go about implementing something like this? I use Laravel as an API only. We have other framework where the login was implemented where it saves an httpOnly cookie (sessionId) after the user logs in. That's the main framework. We're migrating away from that old framework (Zend).
With the sessionId sent to Laravel from, say, a JS frontend, I'm able to lookup the current user based on the sessionId. That sessionId is then used to query the session database. I've created a middleware called "CheckForCurrentUser.php":
[..]
public function handle(Request $request, Closure $next)
{
// The reason for this is that the OPTIONS (request) does not include the cookie in the request.
$method = $request->method();
// SESSIONID is the name of the cookie created from the main framework
// once a user is logged in.
// SESSIONID is an exception in EncryptCookies.php
$sessionId = request()->cookie('SESSIONID');
if ($method === 'POST' && $sessionId !== '') {
// This function is only to get the logged in user id from the session database
$userId = $this->notImportantFunction($sessionId);
if ($userId) {
// User id found so make current user for this Laravel API
Auth::loginUsingId($userId);
} else {
// Instructs the frontend to let user log back in.
return abort(401);
}
}
return $next($request);
}
To recap, a user cannot authenticate from this Laravel application nor can I send an authentication token. They logged in from another framework. Laravel has access to the main framework databases.
This setup works. Using use Illuminate\Support\Facades\Auth; I use that as the "current user" in any controller/model.
Kernel.php looks something like:
[..]
protected $middleware = [
[..]
\App\Http\Middleware\CheckCurrentUser::class,
[..]
];
Before I go any further, is that how you'd implement something like this? It does work (ish) but I do not get the SESSIONID unless I check for a POST request.
I do not like this setup. I'm now using lighthouse and having issues using both the #auth and #inject directives. This is due to how I authenticate a use with Laravel so sorting how I authenticate a user should sort Lighthouse. Any tips on how to refactor this the right way? My routes are with /api/some-string
Lighthouse is not the issue. Should I send a authentication header, Lighthouse works. I believe Laravel does something behind the scenes with it sees a token in the header. I cannot send an authentication. I can only rely on the cookie. It's httpOnly so I have no access to that from JavaScript.

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.

Laravel multiple authentication from two different route and view

I want to implement a system where 6 types of users exist. So one is 'customer' who will login by a route like /login and rest of 5 users are admins and only they will be login using another route /system/base-admin. However, 'customer' never login with the /system/base-admin route if anyhow can known this route. And both route have different login form and if they failed to login 'customer' will be redirected /login and admins /system/base-admin.
I know about $guard and middleware check.
Question: How can i implement above scenario and how react professionals with this scenario?
Route::get('/login','CustomerLoginController#processLogin')->name('customer.login');
Route::get('/system/base-admin', 'AdminLoginController#processAdminLogin')->name('system.admin')
My Controller Looks like
public function processLogin(){ return view('customer.login');}
public function processAdminLogin(){ return view('admin.login')}
Thank you in advance.
The only reason I see to have different endpoints for login is to have different views.
Copy your Auth\LoginController, change $redirectTo to redirect to your admin panel. Overwrite AuthenticatesUsers\showLoginForm to show your admin form and update middleware in __construct.
Protect all your admin routes with admin middleware.
Now. Your users CAN login to your panel. BUT nothing will happen since they don't have access.
If you want to show them some kind of message when they try you can overwrite AuthenticatesUsers\login method with something like this
...
if ($this->attemptLogin($request)) {
if(!auth()->user()->isAdmin()){
throw ValidationException::withMessages([
$this->username() => 'You don\'t have access to this page',
]);
}
return $this->sendLoginResponse($request);
}
...

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