Laravel - Controller dependency is injected before middleware is executed - laravel

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);
}

Related

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 passport - Allow user to act as/login as other user

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
});

having anonymous user on laravel

I'm using Laravel 5.8. And I have created a custom Guard that is using jwt. That I use as a middleware for authenticating users.
I have some routes that need to have different responses based on being an authenticated user or an unauthenticated user. what do you suggest me to do? what is the best practices to implement this?
I can define a custom guard which its check function always returns true.and returning an integer like -1 for unauthenticated user while the user is not authenticated.but it does not sound a clean way of implementing this.
Depending on how you want to set this up, you can just use the Auth facade helpers in your controller method to see whether a user is authenticated or not:
// SomeController.php
public function index(Request $request)
{
if(Auth::guest()) {
return response()->json('i am a guest');
} else {
return response()->json('im not a guest');
}
}
or use any of the related methods:
// Inverse of `Auth::guest()`
$isLoggedIn = Auth::check();
$loggedInUser = Auth::user();
https://laravel.com/api/5.8/Illuminate/Auth/GuardHelpers.html

Use Policies with apiResource Routes

Currently I'm writing a Laravel 5.6 REST api. Now I want to secure my endpoints:
Each user in my application has a role. Based on that the user should be able to access some endpoints and otherwise should get a 403 error. For this I would like to use Policies because, when used as middleware, they can authorize actions before the incoming request even reaches my route or controller.
I declare my endpoints like this:
Route::apiResource('me', 'UserController');
My problem now is that if I want to use Policies as middleware I have to specify the (HTTP) method like this middleware('can:update,post'). How should I do this when I use apiResource in my route declaration?
BTW: Currently I have written a FormRequest for each method (which is a pain) and do the authorization there. Can I simply return true in the authorize method after switching to Policies middleware?
Since you are using FormRequest::class to validate the request data, it is best practice to first check is the user is authorized to make the request. For Laravel 5.6 the cleanest solution would be to specify each policy manually in the __construct() method of your resource controller.
public function __construct()
{
$this->middleware('can:viewAny,App\Post')->only('index');
$this->middleware('can:create,App\Post')->only('store');
$this->middleware('can:view,post')->only('show');
$this->middleware('can:update,post')->only('update');
$this->middleware('can:delete,post')->only('delete');
}
If your were validating form data inside your controller instead of using FormRequest::class, a cleaner solution would be to also authorize the user inside the controller.
public function store(Request $request)
{
$this->authorize('create', Post::class);
// The user is authorized to make this request...
$request->validate([
//Validation Rules
});
// The form data has been successfully validated...
// Controller logic...
}
Since Laravel 5.7 you can do all of this using one line of code on your controller's __construct() method.
public function __construct()
{
$this->authorizeResource(Post::class, 'post');
}
You can define route groups, routes that have a common behaviour (middleware, prefix etc. ).
The following should work:
Route::middleware('can:update,post')->group(function () {
Route::apiResource('me', 'UserController');
//more routes
});
You can prefix routes as well:
Route::middleware('can:update,post')->group(function () {
Route::prefix('users')->group(function () {
Route::apiResource('me', 'UserController'); //Translated to ex: /users/me
Route::prefix('books')->group(function () {
Route::apiResource('{book}', 'UserController'); //Translated to ex: /users/me/book_1
});
});
});
P.S: I haven't used resources before but it should do the job

Controller constructor to check Auth middleware for two different guards

I have a dashboard view that shows certain contain depending on which user is viewing, whether it be an admin or just a regular user.
I can get my admins onto that page, but regular users aren't able to currently because of my middleware guard.
class DashboardController extends Controller {
public function __construct()
{
$this->middleware('auth:admin');
}
public function index()
{
return view('dashboard.index');
}
}
The following code checks on each DashboardController call for auth:admins, but I want regular users to access this too, is there a way to check the auth middleware twice like so?
$this->middleware(['auth:admin','auth']);
So ideally it will check if you're an admin or just a regular auth user.
Also on my view page, when accessing properties of an admin I'm using:
{{ Auth::user()->admin_username }}
Is this normal? I have an admin Model but I'm still accessing it via Auth::user() which feels strange to me, shouldn't it be Auth::admin()->admin_username
Accessing a particular page for users with differing roles is more suited for laravels gates and policy authorization mechanisms.
https://laravel.com/docs/5.5/authorization#writing-gates
These allow you to write fine tuned rules for each use case you have. Simple gates can be defined as closures within your application AuthServiceProvider. For example:
public function boot()
{
$this->registerPolicies();
Gate::define('access-dashboard', function ($user, $post) {
return auth()->check() && (auth()->user()->hasRole('admin') || auth()->user()->hasRole('regular'));
});
}
Then you can use the gate facade wherever necessary, for instance a controller method or constructor.
if (Gate::allows('access-dashboard', $model)) {
// The current user can access dashboard, load their data
}
Alternatively use the can or cant helpers on the user model directly.
if (auth()->user()->can('access-dashboard')) {
//
}
Of course, you can achieve similar via middleware, the advantage of using the above is you can authorize actions at specific points in your code as well as reusability.
As for for last question, as you have it written is correct.
{{ Auth::user()->admin_username }}
Auth::user() or auth()->user() simply returns the currently authenticated user, regardless of their role.
Policies will never work without auth middleware

Resources