I got the following serivce provider:
class CartServiceProvider extends ServiceProvider {
public function boot() {
}
public function register() {
$this->app->singleton(\Alexxosipov\Cart\Cart::class, function($app) {
return new \Alexxosipov\Cart\Cart($app['request']);
});
}
}
And my Cart class, where I got this:
$this->id = (request()->cookie('cart_id')) ? request()->cookie('cart_id') : false;
But request()->cookie('cart_id') returns encrypted string. If I will do it in any controller, it works fine. Why? What should I do to use it in Cart class?Laravel 5.5
The sequences of Laravel bootstrapping a request is this (v5.6): (CMIIW)
index.php is called
\Illuminate\Foundation\Application created
HttpKernel registered. This register your middlewares
Console Kernel registered. This defines console commands
Exception Handler registered. This defines exception handlers
HttpKernel instantiated. This instantiate all middlewares, plus boot/register all your service providers
Create global request instance. Pass it to HttpKernel to handle incoming request.
Middleware EncryptCookies called, cookies decrypted
Sending request to other middlewares to process
Send request to router, router dispatch to controller
...
Before sending response to browser, cookies encrypted back in EncryptCookies
Cookie is remained encrypted in Step 1 - Step 7. Your CartServiceProvider is trying to obtain a cookie that is yet to be decrypted at Step 6, which is not possible. Consider either
Decrypt the cookie by yourself (using just decrypt), or
Make a middleware to instantiate the cart after EncryptCookies. It's a little bit too early to instantiate cart at the bootstrapping service providers phase.
Edit: Add singleton suggestion
I think you could do this:
Create a new method named loadCartFromRequest($request) in your Cart::class. This method help you load a cart instance from request during the middleware phase.
In your CartServiceProvider, you register a singleton of Cart::class as usual, but no need to read the request here.
Create a middleware, called CartMiddleware. This middleware call app(Cart::class)->loadCartFromRequest($request).
Then at any other places that you need the cart instance, you can access your cart model from app(Cart::class).
I hope I understand your requirement correctly :)
Why? Cookie encryption protects data stored in the client's browser.
The How: Laravel uses the EncryptCookies middleware, this middleware would not yet be processed when your service provider is registered but would be processed for the controller since middlewares are in the routing stack before the request is passed to the controller.
Since I don't know about your cart class and its logic, I can't really recommend what you should do. Perhaps you need to think about when you are passing the Request object to your class.
Related
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);
});
}
}
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.
I have a simple case and I need your advice. I am using tymon jwt package. I have JWT middleware and this is the part of it's code:
$user = JWTAuth::parseToken()->authenticate();
if(!$user){
return response()->json(['message'=>trans("responseMessages.user_not_exists")], 403);
}
$request->request->set('user', $user);
what this middleware does, is that it tries to create $user from given jwt token, if it succeeds, user is good to continue. so here is my question, in this code (final line) I pass user object to controller through request, so I can directly have access to user model in controller. I am just interested, is this a good idea? or maybe this will be problematic?
other option is to write $user = JWTAuth::toUser(JWTAuth::getToken()) in controller function or pass user id through request instead of whole model. but in these cases I communicate with database twice, in middleware and in controller in order to get user object.
also I tried to do something like that in controller constructor : $this->user = JWTAuth::toUser(JWTAuth::getToken()), but controller constructor executes before middleware so this one was problematic. so provide me with your ideas and advices if passing user model is good idea or not.
This is an opinionated question, so don't take my answer as your final solution.
I use Slim and made an authenticate-middleware that adds the user object to the request attributes. This is essentially what you are doing.
Keep in mind the folllowing problems though (at least with immutables Request/Response objects like with PSR7):
when you have middlewares BEFORE your authentication middleware (like catching Exceptions), the request does NOT have the user object, because the middlewares work in layers.
Vice versa: if you have middleware that first executes all other middleware and than executes itself
It's just pseudo-code, but you get the idea.
middlewarefunction($request, $response, $nextmiddleware)
{
$nextmiddleware->do($request, $response);
// from here on the $request has nothing set to the request by the $nextMiddleware
// because it is immutable
}
// Edit
If you look at other middlewares, they are setting the request attribute with the decoded JWT token too:
https://github.com/DASPRiD/Helios/blob/master/src/IdentityMiddleware.php
https://github.com/tuupola/slim-jwt-auth/blob/3.x/src/JwtAuthentication.php
I have a Laravel 5.2 app where I'm trying to login a user in an Event Listener. All routes are wrapped in web middleware, I'm using database for the session and I see it creates an entry when the event listener executes, however accessing other pages later behave as if there's no user in session.
Just to test out my code, I added the same listener code in a controller route and that works, it persists. I'm suspecting this has to do with the web middleware not being available to an event listener. I've tried handling the event in a controller, same behaviour.
I've noticed the user_id in the sessions DB table is set to null when logging in from the event listener, but it has the right value when logging in from the controller. I'm not sure why this is or how to change it (explicitly setting the user_id field before logging in has no effect)
Additionally, calling Auth::user() immediately in the event listener after the login returns a valid user ! It's just not available to the rest of the application. I'm suspecting this is because the plugin that fires the event is in a separate mini-application, as seen by the routes. Wrapping it in the same middleware also has no effect.
What do I have to do to get the event listener login to persist across the app ?
// EventListener, doesn't persist
public function handle(Saml2LoginEvent $event)
{
$user = $event->getSaml2User();
$laravelUser = User::where('mail', $user->getAttributes()['mail'][0])->where('active', 1)->first() ;
Auth::guard('web')->login($laravelUser);
}
// UserController, persists !
public function home(Request $request){
$laravelUser = User::where('mail', 'test#mail.com')->where('active', 1)->first() ;
Auth::guard('web')->login($laravelUser);
}
The event listener class needs to be part of the web middleware to have access to session persistence. So adding those classes to the web middleware group solves the problem
Auth::login($user) was not working for me even after trying everything. (Added saml middlewares, etc).
It worked for me after removing all dump(), var_dump(), die(), etc. from the Listener!
I have a simple silex application and using a lot of different externals controllers.
I am using register and mount to connect it to my application.
$app->register($externalController = new ExternalController());
$app->mount('/control', $externalController );
It adds the routes login, logout, etc in its service provider class:
$controllers->get('/start', 'user.controller:loginAction')
->bind('control.start');
I want to add an event or middleware listener to actions provided by it.
I have searched the silex and symfony documentation, but I didn't find an easy way.
I have tried to use $app['controllers'], but this returns a ControllerCollection without any possibility to change something (or I didn't understand it).
What is the recommended way to add a new listener to an existing non self written controller?
I have found one possible way in the meantime to flush the controllers to create the RouteCollection and retrieve it via binding name.
You will receive a Route instance and can use there the normal middleware listener methods like before, after and so on.
$app->flush();
$route = $app['routes']->get('control.start');
$route->before(function(Symfony\Component\HttpFoundation\Request $request) use ($app) {
throw new RuntimeException('You should see me.');
});