JWT Auth doesn't work when __construct method in controller - laravel

In a Laravel Application, the JWT middleware doesn't work properly. I found out, that there is no auth check, when the controller has a __construct method.
class ProjectController extends Controller
{
public $company;
public $user;
public function __construct(Request $request)
{
$this->company = $request->user()->company;
$this->user = $request->user();
}
Api routes:
Route::group(['middleware' => 'jwt.auth'], function () {
Route::resource('/projects', 'Project\\ProjectController');
});
When i comment the __construct method, the system return a 401 as expected. But if the __construct method is not commented, the system returns a 500 because the company can not be found.
Why the __construct method doesn't work with jwt?

This has nothing to do with the jwt middleware, 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 the solution would be to get the user and company from the request in each controller method, but if you want to keep it in the constructor you could implement the following workaround:
class ProjectController extends Controller
{
public $company;
public $user;
public function __construct(Request $request)
{
$this->middleware(function ($request, $next) {
$this->company = $request->user()->company;
$this->user = $request->user();
return $next($request);
});
}
}

Related

Get authenticated User in API controller in Laravel

I want to fetch the Authenticated User's data in API controller. How to do that?
Here is my API\CompanyController
public function selected_company(){
return Auth::user()->id;
}
The error I got through HTTP request...
To convey the comment in a comprehensible manner,
Either your controller should have a middleware, like
public function __construct()
{
$this->middleware('auth:api');
}
Or your route to the api should be passed through the middleware
Route::get('your-api-endpoint')->middleware('auth:api');

Access session data from parent controller w/o passing it in

Can I access session data from Controller, without passing the request from MyController?
class Controller extends BaseController
{
public function __construct()
{
// ** next line throws error:
// "Session store not set on request."
$userdata = request()->session()->get('userdata');
// I want to inject `userdata` into every template without
// passing data from child controllers.
view()->share(['userdata' => $userdata);
}
}
class MyController extends Controller
{
public function __construct(Request $request)
{
// This works, so the data is in fact in the session.
// I don't want to pass it, or `$request` to the parent from here.
$userdata = $request->session()->get('userdata');
...
}
}
The reason it won't be working in your __construct() method is because the StartSession middleware won't have been run yet.
To get around this you can simply use the middleware() method on the controller:
public function __construct()
{
$this->middleware(function ($request, $next) {
$userdata = $request->session()->get('userdata');
view()->share(compact('userdata'));
return $next($request);
});
}
Laravel 5.3 Upgrade guide (Scroll down the Controllers section)
In Laravel 5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly in your controller's constructor.

Laravel dynamic controller middleware

Hi so I have some routes that are saved in the database each of these has its own controller with its middleware and it is routed via this route;
Route::get('{any}', 'RoutingController#index')->where('any', '.*');
and I try to create the new controller as follows;
$container = app();
$route = $container->make(\Illuminate\Routing\Route::class);
$controllerInstance = $container->make($controller);
return (new ControllerDispatcher($container))->dispatch($route, $controllerInstance, $action);
So my HomeController has a middleware here;
public function __construct()
{
$this->middleware('guest');
}
However this doesn't get honoured as I'm guessing its not a new request. Is there any way I can honour this middleware?
The controller dispatcher is not what you want because the middleware is a layer above the controller. You need to run the entire route:
In your RoutingController
public function index() {
//Override your route with what it really needs to do
$route = Route::get(
{any},
'\App\Http\Controllers\HomeController#index'
)->where('any', '.*');
//Re-handle the request. It should hit your new route.
app()->make(\Illuminate\Contracts\Http\Kernel::class)->handle(request());
}
The idea is you overwrite your general route with what it needs to do based on the request. This should only affect a single request.

Testing Laravel API resources with dependency injections and custom requests

Type hinted route parameter does not instantiate when called from a test.
I have a Laravel API Resource Route::apiResource('users', 'Api\UserController');
Here's my update method in the controller:
public function update(UpdateUserRequest $request, User $user)
{
//
}
Inside the UpdateUserRequest:
public function rules()
{
dd($this->route("user"));
}
If I call this endpoint from Postman, I get the full user object back. However, if I call it from a test:
$response = $this->actingAs($this->user)->
json('POST', '/api/users/'.$this->user->id, [
'_method' => 'PUT',
'data' => [
// ...
]
]);
I just get the string "1", not the instantiated User object.
This is probably caused by the \Illuminate\Foundation\Testing\WithoutMiddleware trait being used by your test case.
For posterity, should anyone come across this, route model binding is performed by the \Illuminate\Routing\MiddlewareSubstituteBindings middleware. The WithoutMiddleware trait therefore prevents it from running.
The base Laravel test case provides an undocumented withoutMiddleware() method via /Illuminate/Foundation/Testing/WithoutMiddleware which you can use to get around this, however it may be worth noting that the lead developer of Laravel, Taylor Otwell, recommends testing with all middleware active when possible.
Well, one thing that worked, and I don't know if this is the correct or the "Laravel" way of doing things is to force instantiate the model in the custom request constructor, and to bind the instance inside the test:
In the UpdateUserRequest:
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
In the Test:
$this->user = factory(\App\Models\User::class)->create();
$this->app->instance(\App\Models\User::class, $this->user);

Laravel 5.4 Sessions and Auth::user() not available in controller's constructor

I would like to use a User class throught the application. So, I would like to create CustomUser and then inject it into controllers that need it (it would be most of them).
Now, I create an empty instance in serviceprovider. Next, I want to fill it with data that are already saved in Auth::user(). After long time I have not found where to do it.
Auth::user() is empty in middlewares, but is filled with the user data in controllers. I am missing the step where Laravel queries the database and fills Auth:user() with data. I want to avoid making the same query again.
Thanks for any help!
You can use base controller with __get() method. For example:
class Controller
{
public function __get(string $name)
{
if($name === 'user'){
return Auth::user();
}
return null;
}
}
And in the child controllers can call $this->user
Since Laravel 5.3, you do not have access to sessions in controller constructors. This is because the middleware has not been run yet. I know it's difficult to locate, but in the migration documentation from 5.2 > 5.3 (you're probably on 5.4), it shows that the proper way to resolve data from sessions (which auth() is just a wrapper around a session() call to get the user), is to use the following method:
class MyController extends Controller {
protected $user;
public function __construct() {
$this->middleware(function ($request, $next) {
$this->user= auth()->user();
return $next($request);
});
}
}
Then $this->user will reference the auth user to any methods inside of this controller.
Hopefully his helps.
In Laravel 5.6 i used this
$this->middleware(function ($request, $next) {
$id = Auth::user()->id;
$res = $this->validateAnyFunction($id);
if(!$res){
//to redirect to any other route
return $next(redirect()->route("any")->with("failed","Invalid")->send());
}
//this is used to proccess futher funcitons of controller
return $next($request);
});

Resources