Laravel 5.7: User Not Set When Throwing NotFoundHttpException - laravel

I am overriding the rendering of a couple of exceptions in the App\Exceptions\Handler so I can return custom error pages. However, on some of the exceptions the auth()->user() is not set.
In the render-method, I am just dumping the user like this:
public function render($request, Exception $exception)
{
dd(auth()->user());
return parent::render($request, $exception);
}
When the exceptions are of the type NotFoundHttpException, the user isn't set. What would be the best solution for getting the user in these exceptions?

Here's the solution, but read on for a different recommendation.
By default, Laravel's authentication runs via the session. The session is explicitly started in the web middleware group.
If a route is not wrapped in the web middleware group (as a non-existent / 404 route would be), the middleware doesn't run, and the session isn't started. Therefore Laravel can't know if there is a session user.
Best solution: define a fallback route which was built for exactly this purpose.
Another solution, with gotchas: move the StartSession middleware into the global group that runs on every request.
/app/Http/Kernel.php:
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* #var array
*/
protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
// +++ MOVED SESSION START HERE BELOW +++
\Illuminate\Session\Middleware\StartSession::class,
];
But, should you...?
First, there's a reason the session doesn't load on every request by default: it creates extra overhead that might not be needed on every request. For example, if your website is configured to always use Laravel as the request handler, every 404 will route through Laravel. And there are a lot - crawlers and bots are constantly scouring the web for known insecure web paths. Take a look at your access logs.
Also, if you create self-contained error pages that don't depend on external application logic, you can reuse them at the server level. If Apache or Nginx throws an error, Laravel won't be there at all to dictate the output.
TL;DR: You can enable sessions on 404 and other pages, but understand the trade-offs. Personally, I recommend avoiding application logic on error pages.

Try:
Auth::user()
It should work the same way. Make sure you're including the Auth library.

Related

Laravel - Booting web middleware from an exception's render method

Found few answers on SO but most of them where kind of hacky so I decided to post anyway.
So I'm using Laravel 8 + InertiaJS and my app has a Billing/Plan logic.
Whenever an app user wants to create a resource, the resource's policy perform checks to ensure the user is under its plan allowance.
If the user reached the limit, a custom exception is returned.
The exception returns an Inertia view, which inherit the app's layout (containing a navbar with user details).
class PlanLimitReached extends Exception
{
public function render($request)
{
if ($request->expectsJson()) {
return response()->json([
'error' => 'PlanLimitReached',
'message' => 'You reached the limitation for your current billing plan, please upgrade your account.',
'documentation' => 'https://docs.company.tld'
], 403);
}
return Inertia::render('User/Billing/LimitReached');
}
}
The issue is that component does not render because user variables are not set. It's expected behaviour as you would typically have a template that do not include auth-related logic specifically for 4XX & 5XX errors pages.
But I would rather have a page directly integrated in my app, so I need a way to force this specific exception to force loading the web middleware (exactly what is discussed in https://github.com/laravel/ideas/issues/676).
Unfortunately solutions I found are:
Requiring the web middleware in the global middleware, which is not really feasible as it would mess up my API routes
Creating a route for the error page and then having the exception redirect to this page, it would work but I find this solution quite ugly
Creating a "guest" layout without auth logic for exception pages, pretty ugly IMO
TLDR: Looking for a way to load web middleware from an exception's render method to be able to use auth()->user() in my templates, without redirects or requiring the web middleware globally.

defining route with 'auth:web' middleware in laravel service provider boot() method

im using a package called https://github.com/garygreen/pretty-routes
there is line in its service provider boot() method (here the code)
it is defining a get route with middlewares from its config file(link to the code) I just added 'auth:web' to its config file but it seems the 'auth:web' middleware is called as soon as code reaches the line before Laravel bootstraps its session and etc. when the auth('web')->user() is yet null
What I can not understand is that I do the same exact thing (here the code)with laravel/telescope but it works. why ???
also changing :
Route::get(config('pretty-routes.url'), 'PrettyRoutes\PrettyRoutesController#show')
->name('pretty-routes.show')
->middleware(config('pretty-routes.middlewares'));
to :
$this->app['router']->get(config('pretty-routes.url'), 'PrettyRoutes\PrettyRoutesController#show')
->name('pretty-routes.show')
->middleware(config('pretty-routes.middlewares'));
in service provider seems to solve the problem and make this code behave like the way telescope package use 'auth:web' as middleware.
what's happening ?
You need to have the web middleware applied to any routes you need sessions for, which is what the default authentication system is using. When you apply the auth middleware without this it can't possibly resolve a user since there is no session to be authenticated against.
You need to apply the web middleware and then what ever other middleware you want:
'middlewares' => [
'web', 'auth:web',
],
If you look at the telescope example you provided you will see they also add the web middleware. So you didn't quite "do the same exact thing" as the telescope config.

Where to check if an User is logged in in a Laravel Application?

I've been using your advice and View::sharing all of my important data to all views. However, there is one issue I have encountered.
This code:
if(!Auth::guest()){
$user=Auth::user()->id;
}
else $user=0;
$temp=DB::select('query');
View::share('cartnumber', count($temp));
View::share('cartitems', $temp);
doesn't work when put in AppServiceProvider. Or better, it always sets $user=0, even if I am logged in. I thought it is because AppServiceProvider's boot function executes before the site checks if someone is logged in.
I then tried to use a BaseController with a construct function but that doesn't work either. The only solution that seems to work correctly is putting the code in every single Controller for every view! That actually works, which kind of confirms my theory.
But is there anywhere I can put this code without having to copy/paste it in every single Controller? Thanks in advance!
You'd likely want to put this code later in the request life cycle to guarantee an auth user because as others have mentioned middleware/session code has not occured during this part of the framework booting up. You could use a service class to call in all your controllers to avoid the copy pasting. Or If you'd like to achieve this using code in your service provider you could use a View Composer instead of a share this allows you to define a callback/or class that will be called right before the view is returned
view()->composer(['/uri-that-needs-data'], function ($view) {
if (Auth::check()) {
$cart = DB::query(...)->get();
$view->with('cartitems', $cart);
}
});
Check out https://laravel.com/docs/5.7/views#view-composers for more details.
Auth::user() will be empty until the session middleware has run.
The reason you can't access the user inside your service provider is because that code is run during the "bootstrapping" phase of the application lifecycle, when it's doing things like loading filesystem or cache drivers, long before the request is sent through response handlers (including middleware).
Once the application has been bootstrapped and all service providers
have been registered, the Request will be handed off to the router
for dispatching. The router will dispatch the request to a route or
controller, as well as run any route specific middleware.
Source: https://laravel.com/docs/5.7/lifecycle
If you don't want to copy/paste that code everywhere, then one place to put it is in custom route middleware. You can list it after the auth middleware to guarantee a logged-in user.
Edit: View composers are another really good option, as suggested by #surgiie. The reason these can be set up inside a service provider (unlike your example) is because the view composer registers a callback, but doesn't execute it until a much later stage in the application lifecycle.

Laravel Middleware change response content

For my application I am having multiple user roles and a custom maintenance mode. If the site is in maintenance mode, then depending on the user's role will limit their access to certain pages.
So when "normal" users access the forums, they should instead see a different view saying that the site is in maintenance mode; whereas when "admin" users access the forums they should be able to see the forums.
public function handle($request, Closure $next) {
if(Auth::user()->role->maintenance_mode != 1) {
// They do not have access during maintenance mode,
// so change the response to show a different view.
}
// They do have access during maintenance mode,
// so continue the request.
return $next($request);
}
Is it possible to have the same route (e.g. /forums) but to show a different view, changed by the middleware.
It is possible, yes, however each middleware calls $next($request) which may point to another middleware before the route, therefore returning a view may not be the best idea. If you do want to do it, then you need to do something like: return new Response(view('maintanance')); and don't forget to include use Illuminate\Http\Response; in the header of your Middleware class.
In your case, what I would do is have a route that returns a view such as /maintainance (preferably with a name) and then in the middleware, return a redirect to the route return redirect()->route('maintanance'); within the if.
You could also throw a HttpException with the status code of 503 to have the application invoke Laravel's built in maintenance mode within that first if.

Laravel 5.2 Session not passing

I have a simple success message on store to DB.
\Session::flash('info', 'Success! Words created');
now if I var_dump the session and return that, great.
As soon as I move to another view. Session info is gone!
I've tried all sorts, I'm on laravel 5.1.
looked into the middleware groups but i just get blank pages when adding routes in here..
This is a breaking problem with the 5.2 upgrade. What's happening is the middleware which is responsible for making that errors variable available to all your views is not being utilized because it was moved from the global middleware to the web middleware group.
There are two ways to fix this:
In your kernel.php file(app/Http/Kernel.php), you can move the middleware \Illuminate\View\Middleware\ShareErrorsFromSession::class back to the protected $middleware property.
Wrap all your web routes with a route group and apply the web middleware to them:
Route::group(['middleware' => 'web'], function() {
// Place all your web routes here...(Cut all `Route` which are define in `Route file`, paste here)
});

Resources