Laravel 7 User data in __construct - php

Using Laravel 7.
In the controller constructor, I then hoped to get access to the current user details so I could load main site widgets (buttons links etc) and custom user widgets in to one to be displayed in the view
use Illuminate\Support\Facades\Auth;
...
$widgets = Cache::get("widgets");
$usersdata = Cache::get("userdata");
$this->middleware('auth');
$widgets = array_merge($widgets, $usersdata[Auth::user()->id]["widgets"]);
View::share([
"widgets" => json_encode($widgets)
]);
however at this stage from research the user data is not available (even after authentication ?).
Not sure of best way to access this, or better practice might be to override the middleware auth (where?) so that it could return user id or something eg:
$userid=$this->middleware('auth');
I would like this in the constructor so the same method is in place for all controllers which extend this main controller.

This is intended behavior from laravel, you can read more about it here.
Laravel collects all route specific middlewares first before running
the request through the pipeline, and while collecting the controller
middleware an instance of the controller is created, thus the
constructor is called, however at this point the request isn’t ready
yet.
You can find Taylor's reasoning behind it here:
It’s very bad to use session or auth in your constructor as no request
has happened yet and session and auth are INHERENTLY tied to an HTTP
request. You should receive this request in an actual controller
method which you can call multiple times with multiple different
requests. By forcing your controller to resolve session or auth
information in the constructor you are now forcing your entire
controller to ignore the actual incoming request which can cause
significant problems when testing, etc.
So one solution would be to create a new middleware and then apply it to all routes, something like this, where widgets is your new middleware:
Route::group(['middleware' => ['auth', 'widgets']], function () {
// your routes
});
But if you really want to keep it in the constructor you could implement the following workaround:
class YourController extends Controller
{
public function __construct(Request $request)
{
$this->middleware('auth');
$this->middleware(function ($request, $next) {
$widgets = Cache::get("widgets");
$usersdata = Cache::get("userdata");
$widgets = array_merge($widgets, $usersdata[$request->user()->id]["widgets"]);
View::share([
"widgets" => json_encode($widgets)
]);
return $next($request);
});
}
}

Related

Declare same route twice but expect different behaviour according to a middleware

I started creating a REST API using the lumen framework and wanted to set up a particular behaviour for my GET /user route. Behaviour is the following:
If the request come from an authenticated user (using auth middleware), the method getAllFields from UserController is called and return all the data from the user
If it's not the case, the method get from UserController is called and return some of the data from the user
It seems logic to me to just write it like that in my web.php using a simple middleware:
<?php
$router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('/user/{id}', [
'uses' => 'UserController#getAllFields'
]);
});
$router->get('/user/{id}', [
'uses' => 'UserController#get'
]);
But for some reason, even if the middleware is correct, I always get the response of the second route declaration (that call get()). I precise that if I remove the second route declaration, the one in the middleware work as expected.
Have someone an idea how I can achieve something similar that work?
Router will check if your request matches to any declared route. Middleware will run AFTER that match, so You cannot just return to router and try to find another match.
To fallow Laravel and Routes pattern - You should have single route that will point to method inside controller. Then inside that You can check if user is logged or not and execute getAllFields() from that controller. It will be not much to rewrite since You are currently using UserController in both routes anyway.
web.php
$router->get('/user/{id}', 'UserController#get');
UserController.php
public function get()
{
return auth()->check() ? YourMethodForLogged() : YourMethodForNotLogged();
}
Or if there is not much logic You can keep this in single method.
Also it is good idea to fallow Laravels REST standards (so use show instead of get, "users" instead of "user" etc - read more https://laravel.com/docs/7.x/controllers)
web.php
$router->get('/users/{user}', 'UserController#show');
UserController.php
public function show(User $user)
{
if (auth()->check()) {
//
} else {
//
}
}
To summary - for your needs use Auth inside controller instead of middleware.
To check if user is logged You can use Facade Auth::check() or helper auth()->check(), or opposite Auth::guest() or auth()->guest().
If you are actually using Lumen instead of full Laravel then there is not auth helper by default (You can make own or use package like lumen-helpers) or just keep it simple and use just Facades instead (if You have then enabled in Lumen).
Read more https://laravel.com/docs/7.x/authentication and https://lumen.laravel.com/docs/7.x/authentication
This pattern is against the idea of Laravel's routing. Each route should be defined once.
You can define your route without auth middleware enabled and then define your logic in the controller.

How to fire an event after successful authentication with JWT

I have a Laravel version 6 application. I use JWT package for authentication.
The routes defined like this:
// route/api.php
Route::middleware('auth:api')->group(function () {
Route::apiResources([
'genders' => 'API\GenderController'
]);
});
Inside the controllers I have 5 functions index, store, show, update and destroy.
I need to set the locale for the current user before running any code inside these functions, something like this:
public function index()
{
$locale = request()->user()->lang;
app()->setLocale($locale);
// Rest of the function
}
My controller class extends a BaseController class. As the Restful API is state less I need to set the locale each time the user send a API request. Now the question is that where is the best place to do it and how can I do it?
I tried to do it inside the constructor of the BaseController but it seems the middleware('auth:api') has not yet checked the token in constructor.
The other option is to set the locale inside a authentication event handler. I found a list of JWT package events here:
// fired when the token could not be found in the request
Event::listen('tymon.jwt.absent');
// fired when the token has expired
Event::listen('tymon.jwt.expired');
// fired when the token is found to be invalid
Event::listen('tymon.jwt.invalid');
// fired if the user could not be found (shouldn't really happen)
Event::listen('tymon.jwt.user_not_found');
// fired when the token is valid (User is passed along with event)
Event::listen('tymon.jwt.valid');
If someone could help me to define a handler for tymon.jwt.valid I would be appreciated. Or even if you have some other solution for running an event before execution of index, store, show, update and destroy functions.
You can easily do it in the middleware. Just put middleware that authenticates user before the one that sets location.
Create your route class and register it in App\Http\Kernel::$routeMiddleware
Then use it like this:
// rouite/api.php
Route::group(['middleware' => ['auth:api', 'locale']], function () {
Route::apiResources([
'genders' => 'API\GenderController'
]);
});
I could find a different way for accessing the current user inside the constructor class. While the request()->user() code returns null in constructor, auth()->user() return the current user even in the constructor.
abstract class BaseController extends Controller
{
public function __construct()
{
$locale = auth()->user()->lang;
app()->setLocale($locale);
}
}
Therefore I could set the locale in my BaseController class.
However, my question regarding working with JWT events is open. Also I would like to know more details about working with middleware as #patryk-karczmarczyk suggested in his answer.

Add Routes and Use Session Information in Service Provider

tl;dr; How can I get session data, then based on authentication, manipulate routes in a laravel ServiceProvider?
The Problem
I need to do two things in a service provider. Register routes and use session data at the same time potentially.
Existing Solutions
Route Without Session Data
I know ServiceProvider::register may happen before RouteProvider::register gets called. ServiceProvider::boot can add routes using something like this...
public function boot(Router $router)
{
$router->prefix('api')->group(function ($router) {
$router->resource('lists', '\App\Extensions\ExtensionName\Http\Controllers\MyController');
});
}
Now this will add the route properly. But by the time that route is accessed, there will be no session data. Therefore Auth::user() will return null. And of course Auth::user() is going to return null in the boot() method as well during the adding of routes.
Session Data Without Route
I could extend the \Illuminate\Session\Middleware\StartSession with my own middleware and fire an event like session.started at the end of an overloaded startSession() method. With this, I should have the session data needed to do something like Auth::user().
public function boot(Router $router)
{
Event::listen('session.started', function () use ($router) {
if (Auth::user()->can('do.something')) {
$router->middleware(['auth'])->prefix('api')->group(function ($router) {
$router->resource('lists', '\App\Extensions\ExtensionName\Http\Controllers\MyController');
});
}
});
}
However, the route will not be added at this point as that stage has already been long over with.
Proper Solution Hints
I've been leaning towards registering my routes, then in my controller, I would somehow inject the session middleware so that it starts the session before my controller code actually runs. But I'm unsure how to do something like that.
It would be very nice if I could have access to the session data before even supplying my routes as it would be cleaner, faster and more secure to include routes that users have access to in the first place instead of checking on every call to them or removing them after they've been added and authorization has been checked.
References
How to retrieve session data in service providers in laravel?
https://laracasts.com/discuss/channels/general-discussion/laravel-5-session-data-is-not-accessible-in-the-app-boot-process
https://github.com/laravel/framework/issues/7906
https://github.com/laravel/framework/pull/7933
https://laravel.io/forum/12-17-2014-session-content-not-available-in-service-providers-register
https://josephsilber.com/posts/2017/01/23/getting-current-user-in-laravel-controller-constructor

Laravel - How to restrict certain functionality based on user authentication?

Laravel newbie here (obviously :D ). I've set up a new model & controller for a model named Pages.
Every User has many Pages.
Each Page has a single User.
I've created the following functioning controller actions (& views):
PagesController::index
PagesController::create
PagesController::store
PagesController::show
PagesController::edit
PagesController::delete
So you can edit a Page by going to url.dev/pages/{id}/edit.
The problem is, you can access all of these routes regardless of your session status. So random users can edit any given Page. Which, obviously, is terrible.
Can anyone point me in the direction of what I should read up on, to limit the ability to access my model's controller actions based on whether or not the user is logged in (and if it's the correct user, at all)?
To force a specific route to be only accessible by authenticated users you can specify middleware auth in the controller constructor, like so:
public function __construct()
{
$this->middleware('auth');
}
Also you can restrict which methods you want auth to be applied to in the controller, using the only or except parameters. Using only you could do:
public function __construct()
{
$this->middleware('auth', ['only' => ['create', 'store', 'edit', 'delete']]);
}
You´re looking for middleware..
You can read more here
public function __construct()
{
$this->middleware('auth')->only('index');
$this->middleware('admin')->except('store');
}
Other answers are good, but I prefer to use middleware on route groups.
So when I have several routes like this:
Route::get('pages', 'PagesController#index')->name('pages.index');
Route::get('pages/{id}/edit', 'PagesController#edit')->name('pages.edit');
I would add them inside Laravel Route group. Like this:
Route::group(['middleware' => 'auth'], function() {
Route::get('pages', 'PagesController#index')->name('pages.index');
Route::get('pages/{id}/edit', 'PagesController#edit')->name('pages.edit');
});

What is the purpose of the authorize method in a Request class in Laravel?

I am today in bit confusion about my website security and some extra code that is written to make the website secure. Below are 2 locations where security is applied.
Inside Route Config, To secure the route, I have used Middleware to check the user role.
Route::group(['middleware' => ['web', 'SuperAdmin', 'auth']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
I mentioned 2 Middlewares.
Auth Middleware : This is for authentication.
SuperAdmin Middleware: This is for Authorization.
Second location is Request class. Here is the code. In authorize method, again same thing is being checked as already done in route
class DepartmentRequest extends Request
{
public function authorize()
{
if(\Auth::user() == null) {
return false;
}
if(\Auth::user()->isSuperAdmin()) {
return true;
}
return false;
}
public function rules()
{
return [
'Department' => 'required',
];
}
}
Question: Should I remove check in Request class? Is that an unwanted validation to secure the request ? As route.config is already doing the job.
What's the use of authorize method? I meant, I am using Request class to validate form inputs. Should I always return true from authorize method?
yes, you should remove that checks in the Request class: if you're already doing that checks in your middleware you should not repeat them
When you specify this:
Route::group(['middleware' => ['web', 'SuperAdmin']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
You're telling laravel that, when it finds a /Create-Department route, it should trigger the handle method of these middleware: ['web', 'SuperAdmin'], before the request is sent to the DepartmentController
So, if you check for authentication and authorization in the middlewares, when the request will get to your controller you're sure that it has satisfied all the middleware it went through
Regarding the purpose of the authorize method: the authorize method is usually used to authorize the actual request basing on some policy you'd like to respect. For example, if you have a request to edit a Post model, in the authorize method you'd check that the specific user trying to edit the post has the permissions to do it (for example being the author of the post )
EDIT
Even if you want to use a middleware for your authorization, it's fine. Anyhow, usually the authorize method within form requests is used to do authorization checks on the specific request.
For instance check this example from the docs :
public function authorize()
{
$postId = $this->route('post');
//here the authorization to edit the post is checked through the Gate facade
return Gate::allows('update', Post::findOrFail($postId));
}
In conclusion: if you're doing your authentication and authorization tasks in middlewares, you don't need to repeat them in the authorize method, but keep in mind that the native purpose of the method is to authorize the specific request

Categories