Laravel 5: Sessions on 404 route - php

This is driving me crazy for weeks now:
How can I make sessions available on the 404 page? I just
embed the 404 error page in my default template. It also shows the navbar
and the footer but how can I keep my user logged in when on a 404?
On a 404 Auth::check() always returns false and every else whats session specific is null or empty.
How to enable sessions on (404) error pages?

What you can do is, inside the app/http/Kernel.php add the follwing block of code:
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
Inside the $middleware variable. So it would look like:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
It worked for me, I hope it works for you as well.

So I know this is an old question but in case anyone finds it useful here's how I've dealt with this:
In my App/Exceptions/Handler.php I changed the default render method to:
public function render($request, Exception $exception) {
\Route::any(request()->path(), function () use ($exception, $request) {
return parent::render($request, $exception);
})->middleware('web');
return app()->make(Kernel::class)->handle($request);
}
This achieves 2 requirements:
All the web middleware run correctly for the request
Valid API routes do not start the session
This is in practice a decision to make the web middleware group the default to run when there's an error. Laravel would normally not run any middleware group on error pages. You can of course specify additional middleware or other route parameters if you want or use other conditions like e.g. if the path starts with api/ then use the api middleware instead making it more consistent.
Hopefully this is helpful to someone.

For Laravel 5.5.5 or above just use the Route::fallback:
Route::fallback('PagesController#notFound');
this will help you to customize your 404 views having access to all the session, and more, stuff.

Just to expand a bit on the existing answer: make sure to remove that middleware from $middlewareGroups if it's also there, so you don't apply the middleware twice.
You'll end up with something like this:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
//\Illuminate\Session\Middleware\StartSession::class,
//\Illuminate\Session\Middleware\AuthenticateSession::class,
//\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
As far as my understanding goes this was caused because the middleware that deals with the session, by being at the web group was being applied only to those pages routed on web.php. And since the default error handling does not redirect to a routed page we had no access to the session.
This way the middleware will be applied to all pages, and not only to those routed on web.php, including the error ones.
I originally found the soution here, but took me a while to understand why this was happening (thought i cold have gotten it all wrong, feel free to confirm or correct this please).
Hope it helps, it's working for me on Laravel 5.4

To avoid problems when you have other exception or recive request with api middleware,change the render function In App/Exceptions/Handler.php to :
public function render ($request, Exception $exception)
{
if ($this->isHttpException($exception)) {
switch ($exception->getStatusCode()) {
case '404':
\Route::any(request()->path(), function () use ($exception, $request) {
return parent::render($request, $exception);
})->middleware('web');
return app()->make(Kernel::class)->handle($request);
break;
default:
return $this->renderHttpException($exception);
break;
}
} else {
return parent::render($request, $exception);
}
}

I don't really see how any of these answers are great. They are over complicated and tend to suggest to rebuild the kernel with an additional route, causing a spike in memory and cpu usage just to make a 404 page.
I suggest just creating the route you need, and not using the automatic error pages that the Laravel pages suggest. In newer versions of Laravel this has been improved, but for this version I just recommend the following:
routes.php
Route::group(['middleware' => ['web']], function ($router) {
$router->get('/404', [
'uses' => 'HttpErrorController#404',
'as' => 'errors.404',
]);
});
Obviously depending on how you are set up, this is how I'd use the routes and create a HttpErrorController to handle the route, this why it's already in the web middleware group
App\Exceptions\Handler.php
public function render($request, Exception $exception)
{
if ($this->isHttpException($exception) && $exception->getStatusCode() === 404)
{
return redirect()->route('404');
}
return parent::render($request, $exception);
}
You can handle these any way you see fit, add a switch in there, check if it's a json request and then handle that differently etc

Related

Handling PostTooLargeException in Laravel 5.5

I am trying to handle PostTooLargeException in my Laravel 5.5 application.
When I trying to upload too big file through my form I receive PostTooLargeException which I successfully catch in app\Exceptions\Handler.php, but on that step I would like to redirect user back to the page with form and show an error message.
I wrote following code:
class Handler extends ExceptionHandler
{
...
public function render($request, Exception $exception)
{
...
if($exception instanceof PostTooLargeException){
return redirect()->back()->withErrors("Size of attached file should be less ".ini_get("upload_max_filesize")."B", 'addNote');
}
...
}
}
As a result I was redirected to the proper page but without any message and ViewErrorBag was empty.
Did I something wrong with that redirection?
Thank you a help!
The ViewErrorBag is empty because session is not start in the Handler. Solution was previously described by #Talinon at Laracast
To make session available in the Handler class, I moved \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class from $middleware to $middlewareGroups array at the App/Http/Kernel.php
My updated $middlewareGroups array looks like:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, // <<< this line was added
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
...
];

Session variable isn't set in Laravel

everybody.
I made a middleware. I made a global one putting it into Kernel.php, which is called to every request and which verify if user is loged in.
The point was I needed to use the session for that. But the session was empty. Probably because it is populated later in script.
So I fix it using a global middleware called '\Illuminate\Session\Middleware\StartSession::class'. It was great because after that I could see, in my middleware, what session contains.
But another bug showed up. Since I puted the '\Illuminate\Session\Middleware\StartSession::class' middleware, my redirects doesn't put on session the variable anymore.
Till now code below worked perfectly and redirect created the arsh variable on session:
return redirect('/admin')->with('arsh', $arsh);
But now doesn't put anymore the arsh variable on session on redirect.
I researched a lot on internet, but nothing. I saw a lot of advices but no one worked.
I just don't know what I can do. If you know something would be awesome.
You also can think about using another solving method for reading session in my middleware, and probably this won't need anymore thinking about last bug.
I hope you understood what I wrote and sorry for my english.
Edit:
I did what Hamoud said:
I moved my middleware (\App\Http\Middleware\RedirectIfNotAuthenticated::class,) from $middleware to $middlewareGroups:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrimStrings::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\RedirectIfNotAuthenticated::class, // my middleware
],
'api' => [
'throttle:60,1',
'bindings',
],
];
And now redirect works well and can sets variables on session. But my middleware doesn't work anymore:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Session;
use Request;
class RedirectIfNotAuthenticated
{
/**
* handle
*/
public function handle($request, Closure $next, $guard = null)
{
if ($request->is('admin/*') && !$request->is('admin') && !$request->is('admin/login') && (!Session::has(_DB_PREFIX_.'id_admin') || !Session::has(_DB_PREFIX_.'name_admin'))) {
return redirect('/admin');
}
return $next($request);
}
}
If statement is never true...
That because $middleware is called before verifying the route, while $middlewareGroups is called after. So I am redirect to 404 when my url is something like domain.com/admin/fgtsdr.
What my middleware does is if route is admin/* and I am not loged in, it redirects me to /admin. The point is it has to do that even if route exists or not...
You did not post your Kernel.php file, and it's not clear what do mean by global middleware. Do you add it the $middleware array or $middlewareGroups array?
The order of these middleware matters. In the Kernel.php file there are three arrays in which you can register your middleware.
$middleware. To be triggered with every request.
$middlewareGroups. You register group of middleware to assign to specific route. It has two main groups: web and api. The web group is assigned to all routes registered in the routes/web.php file.
$routeMiddleware. Individual middleware to assign to specific route.
I assume you started by putting your custom middleware in the $middleware to make it global, and when this did not work you added Laravel's StartSession middleware to the same array. However, StartSession is already registered in the $middlewareGroups array within the web group. So, you have two sessions one of them is destroying the other.
The correct way to add a middleware to all routes, when they need a session (web routes), is to add it in the web group if the $middlewareGroups after the StartSession middleware.
For example,
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, <---- Laravel session middleware
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CustomMiddleware::class, <--- Your global middleware.
],

Using a custom middleware redirects me to /home, even if middleware is empty in Laravel

I'm pretty out of any ideas right now.
The case is: I have a route for an API-endpoint (working fine, responding JSON etc.).
If I now apply the built-in 'auth' middleware to the route, I'm redirected ALWAYS to the /home route. Looks like I'm doing sth. wrong with the auth? I think wrong, because:
This curious redirect also kicks in, if I don't use 'auth' but a custom middleware, that contains NOTHING but
public function handle($request, Closure $next)
{
print "WTF";
throw new AuthenticationException('Unauthenticated.');
}
[the print and the Exception are never thrown! I'm landing again without errors at /home.]
Not all middleware is producing this error: For example, 'auth.basic' and 'web' are just working fine with this route.
I also applied 'web' and my own middleware both to the route according to some results I found, that said that using 'web' solved similar problems for them, but guess what, nothing changed.
TL:DR: If I use my own middleware or 'auth' I'm getting redirected, BEFORE the middleware itself is executed.
Update:
After fiddling around with the code and the great tipp from mnies, I found this very curious Bug:
If I just uncomment AuthenticationException, suddenly my code is working as intended. It may be that loading the Exception-Class calls RedirectIfAuthenticated Middleware?! - which is definitely called!
The easy solution is now, using a different Exception for my custom middleware, but the Problem is that the default 'auth'-MW is also using this Exception and so causing the same Bug.
Remember: I am not using other middleware than just this own one, the bug seems really loading the Exception, like WTF?
So I still need help why this is happening!
Bug: using
throw new AuthenticationException('Unauthenticated.', []);
causes 'guest'-MW (RedirectIfAuthenticated) being called instead of intended MW-stack! (nothing of the original MW-stack is being executed, no matter the order.
Update 2:
It seems that RedirectIfAuthenticated is thrown only because I got redirected before to the /login route (and from there as described to /home through it), but that doesn't change the issue that this random redirect occurs.
[I'm trying atm to reproduce this Bug in a fresh installation]
Update 3: I was not able to reproduce the bug in a fresh installation with Laravel 5.4.19.... Trying to compare both installations now. D:
Using Laravel 5.3.30.
Some of my code for context:
Route:
Route::get('/admin/user/get', ['middleware' => ['simpleauth', 'web'], 'uses' => 'UserEditAdmin\Controllers\Controller#get']);
Custom middleware:
class SimpleAuth
{
public function __construct(){}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
* #throws AuthenticationException
*/
public function handle($request, Closure $next)
{
print "WTF";
throw new AuthenticationException('Unauthenticated.');
}
}
'web' from Kernel.php:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
Have a look at your \App\Http\Kernel.php. It looks like you're always calling the \App\Http\Middleware\RedirectIfAuthenticated middleware (aliased to guest). It you want to debug, you could just throw an exception in that middleware to get a stacktrace of what is called when.

Display validation errors in Laravel 5.2 - $errors variable is empty

I am trying to display validation errors when going back to a View, in Laravel 5.2. The $errors variable is empty and in a similar question Laravel 5.2 $errors not appearing in Blade it has been proposed to:
(1) move the ShareErrorsFromSession middleware to the $middleware property
or
(2) wrap the relevant route with web middleware.
Of course, solution 1 (which works) is not applicable in my case because I have some API routes, as it is well stated in Laravel 5.2 validation errors.
To be more specific, I am using multiple authentication guard drivers and routes that use token authentication are stateless. So what us left is solution 2. It should work, but it doesn't work (OR I am missing something).
No matter if I place my routes (the ones in which I want to display validation errors) inside the group:
Route::group(['middleware' => ['web','auth:web']], function () {
...
}
or just inside:
Route::group(['middleware' => 'web'], function () {
...
}
the $errors variable seems empty. Dumping the variable gives:
object(Illuminate\Support\ViewErrorBag)#209 (1) {
["bags":protected]=>
array(0) {
}
}
The web group is typically defined in kernel.php as:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
],
'api' => [
'throttle:60,1',
],
];
My controller looks like:
$form = Input::all();
$rules = Config::get('validation.contact');
$validation = Validator::make($form,$rules);
if ($validation->fails()){
return Redirect::back()->withErrors($validation);
} else {
...
}
Any ideas?
UPDATE (after answered):
You were right Alexander! I missed that recent change. But what about API routes that need not invoke these middlewares? Should we revert this change as described in Web middleware being applied to API routes in Laravel 5.2 ? If yes, then maybe it is better to strip middlewares from web group and put them in a new group (e.g myweb). This way we are not messing with the framework.
If you are running Laravel 5.2.27 and later, you don't need to include "web" middleware in your routes. Because, starting from the mentioned version, it is already implicitly included by default. In fact, re-including that middleware can cause some odd issues. Source: Validation error in Laravel - $errors array does not get populated after the validation failure

disable csrf in laravel for specific route

I've a payment system, where data is submitted to 3rd party site and than hauled back...
When data returns it hits specific url lets say /ok route. $_REQUEST['transaction'].
But because of laravel middleware I'm getting token mismatch. There is no way 3rd party payment API can generate token, so how I disable it? only for this route?
or is there a better option?
Route::get('/payment/ok', 'TransactionsController#Ok');
Route::get('/payment/fail', 'TransactionsController#Fail');
public function Ok( Request $request )
{
$transId = $request->get('trans_id');
if ( isset( $transId ) )
{
return $transId;
}
}
Since version 5.1 Laravel's VerifyCsrfToken middleware allows to specify routes, that are excluded from CSRF validation. In order to achieve that, you need to add the routes to $except array in your App\Http\Middleware\VerifyCsrfToken.php class:
<?php namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
protected $except = [
'payment/*',
];
}
See the docs for more information.
Since Laravel 7.7 you can use method withoutMiddleware eg:
Route::get('/payment/ok', 'TransactionsController#Ok')
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
Route::get('/payment/fail', 'TransactionsController#Fail')
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
The technique described by #jedrzej.kurylo works well for excluding one or two pages.
Here's a different technique if you need to exclude lots of pages from CSRF validation, with more future-proofing.
You can segment your routes, and apply different middleware to each. So you can put your payment routes into a separate route groups, and not apply VerifyCsrfToken to them. Here's how.
1. Create a routes file
You'll notice in your routes directory, you have the following tree:
routes/
routes/api.php
routes/web.php
Create a new file here, routes/payment.php, and add your routes above to it:
<?php
use Illuminate\Support\Facades\Route;
Route::get('/payment/ok', 'TransactionsController#Ok');
Route::get('/payment/fail', 'TransactionsController#Fail');
2. Process the route with the RouteServiceProvider
In Laravel, Routes are processed by app\Providers\RouteServiceProvider.php. You'll notice these functions: map() and mapWebRoutes(). Add to this file accordingly (I've excluded the stock comments for brevity).
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
$this->mapPaymentRoutes(); // <---- add this line
}
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
protected function mapPaymentRoutes() // <--- Add this method
{
Route::middleware('payment') // <--- this line is important
->namespace($this->namespace)
->group(base_path('routes/payment.php'));
}
Notice we've added a new middleware layer. This is important for the next step.
3. Add a new middleware layer
Your middleware for your route groups are defined in App\Http\Kernel.php.
Update the $middlewareGroups property, and add a middle entry for 'payment'. It can be exactly the same as web, but without the VerifyCsrfToken line.
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\NoClickjack::class,
\App\Http\Middleware\SecureReferrerPolicy::class,
\App\Http\Middleware\NoXssScripting::class,
],
// ********** Add this *******************
'payment' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
// This is the line you want to comment-out / remove
// \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\NoClickjack::class,
\App\Http\Middleware\SecureReferrerPolicy::class,
\App\Http\Middleware\NoXssScripting::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
🎉
Now whenever you add new routes that need to be excluded from the CSRF Token check, add them to the routes/payment.php file.

Categories