New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.2] Weird session behaviour with routes in web middleware group #13000

Closed
dciancu opened this Issue Apr 3, 2016 · 23 comments

Comments

Projects
None yet
@dciancu
Contributor

dciancu commented Apr 3, 2016

Hey guys,

This has been one of the most frustrating issues I've had with Laravel (and I'm not the only one).
Thanks God I found what caused it.

Since 5.2 we had to put all routes that required sessions in the web middleware group. However since 5.2.27 all routes are registered in the web middleware group by default. You would have to load another routes file with the RouteServiceProvider if you want them registered outside the web middleware group or remove the web middleware group from the RouteServiceProvider and register them manually again just like before 5.2.27.

Now the problem is with developers who have not managed to keep up with all the latest changes in Laravel and they don't know that routes are registered by default in the web middleware group. So they put all their routes that require sessions in the web middleware group manually, expecting the old behaviour (before 5.2.27).

And now the inevitable weirdest thing happens no matter the sessions driver being used:

  • first GET request will create a new session
  • second GET request will do nothing (only the client cookie expire time is updated, last_activity in the session storage is not updated)
  • third GET request will create a new session
  • and the cycle continues over and over
  • POST requests will always create new sessions

This issue occurs because we have the web middleware group registered twice. So to fix this, we have to check when the middleware is registered that it was not previously registered somewhere else in the middleware stack. Normally you would not end up in this situation by manually assigning middleware in the routes file, but because this is done by default on new projects by the RouteServiceProvider and developers don't know about it, it just happens! And besides this, I find it a good check to have.

I was talking with someone on Slack who experienced the same behaviour and he was not able to work on his new project for two days because of this issue, just like me.

@eness

This comment has been minimized.

eness commented Apr 3, 2016

I've been struggling with this problem for 2 days so far and just found a solution which is not really documented.

Change the defination of your Route groups with a middleware, from :

Route::group(['middleware' => 'web'], function () {

to

Route::group(['middlewareGroups' => 'web'], function () {

then your sessions and middlewares will work as expected.

@dciancu

This comment has been minimized.

Contributor

dciancu commented Apr 3, 2016

@eness While you can do that (haven't tested), I would not recommend it, because you will create the impression in your app that routes outside the web middleware group don't have sessions enabled, when in fact they do.

As stated above, you got two options:

  1. If you want to maintain the previous behaviour and manually register in your routes.php file which routes belong to the web middleware group you have to remove the web middleware from here.
  2. If you want the new behaviour, everything comes set up straight out of the box (the Laravel way), and all the routes defined in your routes.php file will have the web middleware applied. If you have routes which you don't want to have the web middleware group applied, you can create another routes file and register it in the RouteServiceProvider.
@dciancu

This comment has been minimized.

Contributor

dciancu commented Apr 4, 2016

After a closer thinking on the matter and seeing @eness response I realised that we should really not implement such a check. In my opinion at least, it will do more bad than good on the long term. Why? It creates the impression that middleware registered upper in the stack is not applied on routes where you re-register it. For this particular issue for example, it will create the impression that routes outside the web middleware group will not have it applied and they will not have sessions enabled or the rest of the web middleware stack, when in fact this is not true and all routes will have the web middleware group applied and session enabled along with all the web middleware stack.

So I will go ahead and close this issue now. I'm glad I wrote it, because developers will now be able to only search the issues to find the answer and get it right and not spend precious time thinking in the abyss.

@dciancu dciancu closed this Apr 4, 2016

@dciancu dciancu changed the title from Weird session behaviour with routes in web middleware group to [5.2] Weird session behaviour with routes in web middleware group Apr 6, 2016

@cnweibo

This comment has been minimized.

cnweibo commented Apr 28, 2016

I also experienced this issue for 2 days, and I found following make sense:

  1. Not wrap routes in routes.php with 'web' middleware;
  2. Never var_dump in your application;
    those 2 points solve my session issue pretty well.
    Hope it is help to others.
@Frisbetarian

This comment has been minimized.

Frisbetarian commented May 10, 2016

Stuck on this issue and absolutely nothing works. Is anyone having any luck?

I even tried adding StartSession to the global middleware stack and the sessions are still not persisting after redirecting from a login/register.

@eness

This comment has been minimized.

eness commented May 10, 2016

@Frisbetarian follow my suggestion, it should work.

@DarrenChowles

This comment has been minimized.

DarrenChowles commented May 16, 2016

Thanks for the explanation, I was tearing my hair out with this issue. I'm not entirely sure how something like this can be part of a minor update.

vpratfr added a commit to marvinlabs/laralabs that referenced this issue Jul 1, 2016

@stevenmusumeche

This comment has been minimized.

stevenmusumeche commented Jul 21, 2016

This was also causing very strange behavior which was override custom trusted proxies that I had set. Removing the explicit web middleware from my routes.php file solved it. Frustrating that breaking changes like this occur in minor releases.

@alfaraj

This comment has been minimized.

alfaraj commented Aug 4, 2016

I have the same issue, tried removing the web middleware from routes, but the issue still occurring. Been struggling with this issue for a long time, done many researches with no benefit. Any recommendations?

@loren138

This comment has been minimized.

loren138 commented Aug 12, 2016

You might try editing App/Providers/RouteServiceProvider

The bottom of it is:

protected function mapWebRoutes(Router $router)
{
    $router->group([
        'namespace' => $this->namespace, 'middleware' => 'web',
    ], function ($router) {
        require app_path('Http/routes.php');
    });
}

Which I think is what changes the middleware so you could take out the 'middleware' => 'web' part and I think it would revert to the old behavior.

loren138 added a commit to loren138/docs that referenced this issue Aug 12, 2016

Routes for packages with working sessions
I've created a Laravel Package (https://github.com/loren138/cas-server) which uses sessions to store data about an active CAS Single Sign on Session, however, I had issues storing that data in the session.

First, I realized the 'web' middleware does not automatically get added to package route files (like for the normal routes.php file).  Then, I tried to add it using ``$this->middleware('web');`` in the controller which generated a session, but didn't save anything.  (I'm guessing this is related to laravel/framework#13000.)

So I altered my service provider to load routes like this (following the model of the routing service provider):

        if (!$this->app->routesAreCached()) {
                $router->group([
                    'middleware' => 'web',
                ], function () {
                    require __DIR__.'/../resources/routes.php';
                });
        }

This worked.

I wasn't sure if I should remove the old documentation or not since some packagers may want to use only their own middleware.  Please let me know of anyways I should improve this and if I should do a separate pull request for the 5.3 and master branches.
@kohloth

This comment has been minimized.

kohloth commented Dec 30, 2016

I am experiencing issues as described by @danieliancu in the original post. Sessions don't seem to persist, and session files are visibly generated within the storage/framework/sessions file with each HTTP request.

I've tried:

  • Wrapping my routes with Route::group(['middleware' => 'web'], function () {
  • Wrapping my routes with Route::group(['middlewareGroups' => 'web'], function () {
  • Wrapping my routes with nothing.
  • Clearing my artisan cache
  • Clearing my browser cache / using incognito mode
  • Updating laravel with "composer update" (laravel appears in composer.json as "laravel/framework": "5.2.*")
  • Adding csrf data to page header and inside jQuery setup ($.ajaxSetup({headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}});)
  • Removing the "'middleware' => 'web'," argument from mapWebRoutes within RouteServiceProvider.php and then trying out combinations of adding route wrapping and removing route wrapping from the routes.php file.

No matter what I do, the net effect of this is always one of the following:

  • Sessions don't work like they should, and Auth::user() always returns null, even after I've sucessfully logged in with Auth::attempt().
  • I get a TokenMismatchException with any ajax request

Naturally, this makes it impossible to do any user authentication. Not sure how to fix this... Help would be appreciated!

"php artisan --version" outputs:
"Laravel Framework version 5.2.45"

@cnweibo

This comment has been minimized.

cnweibo commented Jan 4, 2017

@kohloth , you should not wrap any route within web middleware group in your routes.php file since this is done by laravel by default. If you remove the wrapper of Route::group(['middleware' => 'web'], it should work

@tomhatzer

This comment has been minimized.

tomhatzer commented Jan 8, 2017

I had the same problems like @kohloth in Laravel 5.3.29. I got them sorted by moving the contents of the web middleware in Kernel.php to the overall middleware array.

Before:

 /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    ];
    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    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',
        ],
    ];

After:

 /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
            \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,**
    ];
    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
        ],
        'api' => [
            'throttle:60,1',
        ],
    ];
@loren138

This comment has been minimized.

loren138 commented Jan 8, 2017

Sounds to me like something was messed up in your routes file or you RouteServiceProvider. The web routes should be in /routes/web.php.

Moving the middleware will mess you up if you try to use API routes later. If you don't use API routes, it'll be ok, but they may not work right if you try to use them later with this setup.

/app/Providers/RouteSerivceProvider.php should look like this:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * This namespace is applied to your controller routes.
     *
     * In addition, it is set as the URL generator's root namespace.
     *
     * @var string
     */
    protected $namespace = 'App\Http\Controllers';

    /**
     * Define your route model bindings, pattern filters, etc.
     *
     * @return void
     */
    public function boot()
    {
        //

        parent::boot();
    }

    /**
     * Define the routes for the application.
     *
     * @return void
     */
    public function map()
    {
        $this->mapWebRoutes();

        $this->mapApiRoutes();

        //
    }

    /**
     * Define the "web" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     *
     * @return void
     */
    protected function mapWebRoutes()
    {
        Route::group([
            'middleware' => 'web',
            'namespace' => $this->namespace,
        ], function ($router) {
            require base_path('routes/web.php');
        });
    }

    /**
     * Define the "api" routes for the application.
     *
     * These routes are typically stateless.
     *
     * @return void
     */
    protected function mapApiRoutes()
    {
        Route::group([
            'middleware' => 'api',
            'namespace' => $this->namespace,
            'prefix' => 'api',
        ], function ($router) {
            require base_path('routes/api.php');
        });
    }
}
@kohloth

This comment has been minimized.

kohloth commented Jan 8, 2017

@loren138 This didn't quite work for me. Using this code in the Route Service Provider threw an error about overridden methods being incompatible with those of the parent class. I wonder if its because this code is not compatible with version 5.2.45.

After some further investigation, I believe that I have solved the issue. I was trying to log users in via AJAX. I experimented with a more typical POST login form, and it works. However, I still need to find out what the framework is doing that causes AJAX login requests to be ephemeral; working initially, but causing subsequent HTTP requests disregard this prior, successful login.

// On ajax HTTP request
Auth::attempt($username, $password);
$User = Auth::user();
var_dump($User);
// Outputs user object - login worked!

//...then on next Http request
$User = Auth::user();
var_dump($User);
// Outputs null - like login never happened. Most likely session data not stored previously.

EDIT: Found the answer to the issue here:
http://stackoverflow.com/questions/23536877/laravel-4-persisting-data-ongoing-jquery-ajax-submit

To make the session persist, when performing an AJAX login, you must replace:
echo json_encode($array);

with:
return Response::json($array);

@loren138

This comment has been minimized.

loren138 commented Jan 8, 2017

Ah, sorry, I posted the 5.3.x version of that file not 5.2.

That's interesting about using the response object and a good fact to know for the future for me.

@Qh0stM4N

This comment has been minimized.

Qh0stM4N commented Mar 10, 2017

@kohloth Check yours routes for php artisan route:list command all route is web midleware.
I'm fix same problems with project laravel 5.4.15 upragate.

@baxterw3b

This comment has been minimized.

baxterw3b commented Mar 20, 2017

Hi guys, i have a problem with validation errors, basically i validate required fields and is like it works but it doesn't render the errors on the page.
here my code

public function Store(Request $request)
	{

		$this->validate($request, [
			'title' => 'required',
			'body' => 'required'
		]);

		$post = new Post;

		$post->title = request('title');
		$post->body = request('body');

		$post->save();

		return redirect('/');
	}

and here the view


@extends ('layout')

@section ('content')

	<ul>
		<h2>Posts</h2>

		<br><br>

		<a href="/posts/create">New Post</a>

		<br><br>

		@foreach($posts as $post)

		<li>
			<h3><a href="/posts/{{$post->id}}">{{ $post->title }}</a></h3>
			<p>{{ $post->body }}</p>
		</li>

		@endforeach

	</ul>


	<div class="alert alert-danger">
		<ul>
			@foreach($errors->all() as $error)

			<li>{{ $error }}</li>

			@endforeach
		</ul>
	</div>

@endsection

if i dd the request i see this in the shared property

 #shared: array:3 [
              "__env" => Factory {#90}
              "app" => Application {#3}
              "errors" => ViewErrorBag {#163
                #bags: []
              }
            ]
@kohloth

This comment has been minimized.

kohloth commented Mar 20, 2017

@baxterw3b I believe that this is your problem:
return redirect('/');

If I am not mistaken, the error variables only persist for the current request. They are not flashed to the session. Therefore, doing a redirect will effectively delete the errors. Try this:

return redirect('/')->withErrors()

@baxterw3b

This comment has been minimized.

baxterw3b commented Mar 21, 2017

I tried but it doesn't work, and btw that redirect it happen when the user pass the validation, i don't know is really weird.

@baxterw3b

This comment has been minimized.

baxterw3b commented Mar 21, 2017

I'm putting my routes in the web.php, could be that it doesn't use the middleware for the validation?

@baxterw3b

This comment has been minimized.

baxterw3b commented Mar 21, 2017

ahahha i found the error, i think yesterday i was to tired, basically i was echoing out the errors in the wrong view! LOL. thank you man.

@sbourdette

This comment has been minimized.

sbourdette commented Aug 29, 2018

Hello I have the similar problem but i don't understand how to solve it.

After login, i put an object in session.
Then in my custom middleware i want to use this object but it's empty.

My web.php :

Route::get('/shareaccess/{albumid}/{shareid}', 'ShareAccessController@show')->name('shareaccess');
Route::post('/shareaccess/{albumid}/{shareid}/validate', 'ShareAccessController@login')->name('loginshareaccess');

Auth::routes();

// Main Route After Authentification
Route::get('/home', 'AlbumController@home')->name('home');

//Album Routes`
Route::get('/album/{albumid}', 'AlbumController@index')->name('album');

My kernel.php

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,
    // Dev tools for debugging
    \Clockwork\Support\Laravel\ClockworkMiddleware::class,
];

/**
 * The application's route middleware groups.
 *
 * @var array
 */
protected $middlewareGroups = [
    'web' => [
        //note that all route specified in web.php applies this middleware
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class, //https://kfirba.me/blog/the-undocumented-authenticatesession-middleware-decoded
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \App\Http\Middleware\IsShare::class,
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
    ],
];

/**
 * The application's route middleware.
 *
 * These middleware may be assigned to groups or used individually.
 *
 * @var array
 */
protected $routeMiddleware = [
    'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'is_admin' =>  \App\Http\Middleware\IsAdmin::class,
];

My MiddleWare IsShare.php

public function handle($request, Closure $next)
{

    log::debug('ShareMiddleware : user : ' . \Auth::user());

    if (\Auth::user()) {
        if(\Auth::user()->isShare()) {
            $share = Session::get('share');
                //do some stuff
                return $next($request);
        }
    }
return $next($request);
}

My Controller : shareaccess.php

     public function show($albumid, $shareid, Request $request)
     {
         log::debug('ShareAccessController show : albumid : ' . $albumid . ' shareid : ' . $shareid);

         return view('auth.shareaccess', ['albumid' => $albumid, 'shareid' => $shareid]);
     }

     public function login($albumid, $shareid, Request $request)
     {
         $this->validate($request, [
             'email' => ['required', 'email'],
          ]);

         $album = Album::find($albumid);
         log::debug('ShareAccessController login : albumid : ' . $albumid);
         $share = $album->shares()-> where('id', $shareid)->where('email', $request->email)->first();

         if ($share) {
             log::debug('ShareAccessController login : share found : ' . $share->email . ' ' . $share->active );
             $user = User::where('email', 'share@bourdette.com')->first();
             Auth::loginUsingId($user->id);
             log::debug('ShareAccessController : login : user authenticated : ' . Auth::user()->email);

             Session::put('share', $share);

             log::debug('ShareAccessController login : share in Session  : ' . Session::get('share'));
         }
         log::debug('validateshare : route : ' . route('album', ['albumid' => $albumid]));

         return redirect()->route('album', ['albumid' => $albumid]);

 //        return redirect()->back();
     }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment