laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.45k stars 11k forks source link

[5.6.28] Session fails to persist after being expired - no matter the environment (OS/browser/PHP) #25081

Closed MatusMak closed 6 years ago

MatusMak commented 6 years ago

NOTE: I have seen a lot of issue related to Session, but this it is not browser, OS or PHP related and I also reproduced it on a fresh Laravel installation. I have also already asked for help on StackOverflow but nobody knew the solution.

I have a very weird issue that we faced during development. Once my session expires due to not being refreshed for too long, a new session will be created as expected. However, if I am browsing on public site (which is not under Auth protection), the session will not persist and with each get/post request, it will be ignored and a new one will be created (I can see that on XRSF token and new file being created in storage/framework).

Here are few scenarios:

  1. If no user is logged in, everything works fine.

  2. If admin user is logged in (with "Remember me" being checked), but public user is not, accessing any public (unprotected) route (which uses default web middleware) will recreate session as described above. However, once I access any route under admin middlware (web middleware + auth check), session will stick and after that, the issue for public route disappears.

  3. If both users are logged in and I access any protected route, session sticks immediately.

I tried the following, without any success:

What resolves the issue:

Environments where I reproduced the issue:

  1. My PC

    • Windows 10 Pro x64, build 1709
    • PHP 7.2.0 (XAMPP)
  2. My laptop

    • Windows 10 Pro x64, build 1803
    • PHP 7.2.7 (XAMPP)
  3. Colleague's laptop

    • macOS High Sierra 10.13.6
    • PHP 7.2.1 (MAMP)
  4. Free hosting (Endora.cz)

    • CentOS
    • PHP 7.1.16

What probably does not cause the issue:

What might caused the issue:

Steps to reproduce (ONLINE):

On this environment, SESSION_LIFETIME was set to 1 so it can be quickly reproduced.

You can download this demo here from BitBucket


Here are some files that might be useful for you:

.env

APP_NAME=MySite
APP_ENV=local
APP_KEY=base64:dEoI03jGqlhIZS4om6sx7j7aFMmKEweJpN72PijsCTQ=
APP_DEBUG=true
APP_URL=http://mySite

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=mySite
DB_USERNAME=root
DB_PASSWORD=password

BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
SESSION_LIFETIME=120
QUEUE_DRIVER=sync

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

RouteServiceProvider.php

<?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->mapApiRoutes();

        $this->mapPublicRoutes();

        $this->mapOrganizationRoutes();

        $this->mapVolunteerRoutes();

        $this->mapAdminRoutes();
    }

    /**
     * Define the "web" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     *
     * @return void
     */
    protected function mapAdminRoutes()
    {
        Route::middleware('admin')
             ->prefix("admin")
             ->namespace($this->namespace."\Admin")
             ->group(base_path('routes/admin.php'));
    }

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

    /**
     * Define the "organization" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     * It also contains auth protection and nav builder
     *
     * @return void
     */
    protected function mapOrganizationRoutes()
    {
        Route::middleware('organization')
             ->prefix("organization")
             ->namespace($this->namespace . "\Organization")
             ->group(base_path('routes/organization.php'));
    }

    /**
     * Define the "volunteer" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     * It also contains auth protection and nav builder
     *
     * @return void
     */
    protected function mapVolunteerRoutes()
    {
        Route::middleware('volunteer')
             ->prefix("volunteer")
             ->namespace($this->namespace . "\Volunteer")
             ->group(base_path('routes/volunteer.php'));
    }

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

routes/public.php

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register public routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', "LandingController@getIndex");

Route::group(["prefix" => "/auth"], function () {

    Route::get('/logout/{guard}', 'Common\AuthController@getLogout')->name('logout.get');

    Route::get('/login', 'Common\AuthController@getLogin')->name('login.get');
    Route::post('/login', 'Common\AuthController@postLogin')->name('login.post');

    Route::get('/register/{guard}', 'Common\RegistrationController@getRegister')->name('register.get');
    Route::post('/register/{guard}', 'Common\RegistrationController@postRegister')->name('register.post');

    Route::get("/register/success/{guard}", "Common\RegistrationController@getSuccess")->name("register.success.get");

});

Route::group(["prefix" => "admin/auth"], function() {

    Route::get("/login", "Admin\AuthController@getLogin")->name("admin.auth.login.get");
    Route::post("/login", "Admin\AuthController@postLogin")->name("admin.auth.login.post");

    Route::get("/logout", "Admin\AuthController@getLogout")->name("admin.auth.logout.get");

});

routes/admin.php

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "admin" middleware group. Routes are prefixed with "/admin"
| Now create something great!
|
*/

Route::get("/", "DashboardController@getIndex")->name("admin.dashboard");

// rest omitted

Http/Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * 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,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::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\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

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

        'admin' => [
            'web',
            'auth:admin',
            'nav:admin', // nav is custom middleware for loading navigation config to nwidart/laravel-menus library
        ],

        'organization' => [
            'web',
            'auth:organization',
            'nav:organization',
        ],

        'volunteer' => [
            'web',
            'auth:volunteer',
            'nav:volunteer',
        ],
    ];

    /**
     * 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' => \App\Http\Middleware\AuthMiddleware::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'nav' => \App\Http\Middleware\NavigationBuilder::class,
    ];
}

AuthMiddlware.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class AuthMiddleware {
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard) {
        if (!Auth::guard($guard)->check()) {
            return redirect()->route(config("auth.guards.$guard.redirect"));
        }

        return $next($request);
    }
}
MatusMak commented 6 years ago

An update - by accident, I discovered that it is barryvdh/laravel-debugbar that is causing this issue. When I uninstalled it, everything started to work correctly. My bad for not trying this out sooner, I thought it was my friend, not enemy.