[Mis]-Understanding Slim & Sessions

I’ve been all over, up n down with sessions and I simply cannot get anything working using them.

Essentially, here’s the logic I’m attempting to work out:

  1. Initial request - redirected to login interface
  2. Login interface form renders
  3. User submits login form
  4. Password is verified
  5. Create a session variable
  6. Home page interface rendered
    6a) Check session variable on every interface request {or redirect to login}
  7. Destroy session on logout

This is what the settings.php file looks like:

return [
'settings' => [
    'displayErrorDetails' => true, // set to false in production
    'addContentLengthHeader' => false, // allow the web server to send the content-length header

    // renderer settings
    'renderer' => [
        'template_path' => __DIR__ . '/../public/assets/templates/default/',
    ],

    // monolog settings
    'logger' => [
        'name' => 'infraweb-slim-app',
        'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/infraweb.app.log',
        'level' => \Monolog\Logger::DEBUG,  
    ],

    // session settings
    'session' => [
        // session cookie settings
        'name'              => 'INFRAWEB',
        'cache_expire'      => 60,
        'cache_limiter'     => '',
        'gc_probability'    => 1,
        'gc_divisor'        => 1,
        'gc_maxlifetime'    => 30 * 24 * 60 * 60,
        'cookie_httponly'   => true,
        'cookie_secure'     => false,
    ],
],

];

In the dependencies.php file:

....
// slim3-session
$container['session'] = function ($container) {
    $settings = $container->get('settings');
    $adapter = new PhpSessionAdapter();
    $session = new Session($adapter);
    $session->setOptions($settings['session']);
    return $session;
};
....

The contents of middleware.php:

$app->add(
    new Infraweb\Toolkit\Middleware\ValidateUI()
);

$app->add(
    function ($request, $response, $next) {
        $logger = $this->get('logger');
        $session = $this->get('session');
        $session->start();
        $logger->addInfo(__METHOD__.' : slim-php session middleware started - calling next middleware');
        $response = $next($request, $response);
        $session->save();
        $logger->addInfo(__METHOD__.' : slim-php session middleware saved - returning response');
        return $response;
    }
);

These are three route definitions that I’m essentially attempting to get employee login authentication working - all are class structures with method closures for the routing, with ui-named routes providing interfaces and the svc-login has logic intended to reroute requests based on authentication and the route requested:

$app->get('/login', Infraweb\LoginUI::class.':login')
->setName('ui-login');

$app->get('/', Infraweb\IndexUI::class.':home')
->setName('ui-home');

$app->post('/employees/login', Infraweb\Toolkit\Services\Authentication::class.':login')
->setName('svc-login');

The two middleware classes are being loaded up, I can move the order around by calling the $response = $next($request, $response); statement so I can visualize how that’s executing, but no matter what I’m doing I simply cannot get this session stuff worked out where it’s saving the session info - this is the LoginUI implementation:

public function __construct(ContainerInterface $container) {
    // psr container, logger & session reference aggregate(s)
    $this->container = $container;
    $this->logger = $this->container->get('logger');
    $this->session = $this->container->get('session');
    $this->router = $this->container->get('router');
    
    // local api service manager reference
    $this->_service_manager = new EmployeeManager;

    $this->logger->addInfo(__CLASS__ . ' : constucted and overhead initialized');
}

public function login($request, $response, $args) 
{
    // get status of 'active' login
    $tokens = $request->getParsedBody();
    $employee_manager = new EmployeeManager;
    $authenticated = $employee_manager->isActiveEmployee($tokens['employee_username']);

    // perform employee validation
    if(!$authenticated || $authenticated['employee_number'] === 0) {
        // no valid employee number returned - {inactive}
        $this->session->set('eid', 0);
        $this->session->set('authenticated', false);
        $this->session->save();
        $this->logger->addInfo(__METHOD__.' : inactive employee - redirecting to login interface');
        return $response->withHeader('Location', $this->router->pathFor('ui-login'));
    } else {
        // valid employee number returned - {active}
        $employee = $employee_manager->getEmployee($authenticated['employee_number']);

        // varify the {salted} password and return
        if(password_verify($tokens['employee_password'], $employee['password'])) {
            // store the session employee information & complete the callback
            $this->session->set('eid', (int)$employee['employee_number']);
            $this->session->set('fullname', $employee['employee_firstname'].''.$employee['lastname']);
            $this->session->set('authenticated', true);
            $this->session->set('employee', serialize($employee));
            $this->session->save();
            $this->logger->addInfo(__METHOD__.' : active employee #['.$this->session->get('eid').'] - redirecting to home interface');
            return $response->withHeader('Location', $this->router->pathFor('ui-home'));
        } else {
            // invalidate (unathenticated) employee & complete the callback
            $this->session->set('eid', 0);
            $this->session->set('authenticated', false);
            $this->session->save();
            $this->logger->addInfo(__METHOD__.' : credentials failure - redirecting to login interface');
            return $response->withHeader('Location', $this->router->pathFor('ui-login'));
        }
    }
}

And in the authenticate ui middleware exists this logic:

public function __invoke(Request $request, Response $response, $next) 
{
    //echo('<pre>'.var_dump($next->getContainer()->get('router')).'<pre>'); exit();
    $router = $next->getContainer()->get('router');
    $session = $next->getContainer()->get('session');
    $logger = $next->getContainer()->get('logger');

    $logger->addInfo(__METHOD__.' : middleware ui authorization check');

    // authorization check
    if(!in_array($request->getUri()->getPath(), $this->unsecured)) {
        if(!$session->has('authenticated')) {
            $logger->addInfo(__METHOD__.' : unauthorized employee form request, redirecting to login interface');
            return $response->withHeader('Location', $router->pathFor('ui-login'));
        }
    }

    // pass on down the middleware chain
    $response = $next($request, $response);

    $logger->addInfo(__METHOD__.' : authorized, responding to initial request.');

    // pass the response (return) back up the chain to display the requested route ui
    return $response;
}

Apologies for Playing the Fool here, but it seems like I’m encountering a constant redirection to the login interface or the requests being honored (authenticated or not) depending on where the $next() method is called in the sequence. I hope I didn’t clutter this post up with code for no reason, it wasn’t the intention - just what I believe is the code that’s relevant to this enigma of sorts.

Ideas, thoughts, constructive criticisms, bitch slaps and/or shin kicks - bring em on. My slim framework education has gone awry here (grins sheepishly) and any experienced voices are welcomed to dogpile on this - I’m pretty certain at this point there’s some cement-headed logical mistake(s) I’m making…

Thanx in advance!

1 Like

I couldn’t see how the session started. Maybe try to add a middleware to start the session.

Example:

$app->add(function(Request $request, Response $response, $next) {
     $session = $this->container->get(Session::class);
     $session->start();
     $response = $next($request, $response);
     $session->save();

     return $response;
)};

Hey @odan - thanx for chiming in…

The snippets I placed above, the session start call is in the middleware below the ValidateUI class invocation in the middleware piece. I went as far as to remove any existing middleware, take the snippet you posted and adjusted it (the $this object IS the container reference according to a debugger dump) and the code wouldn’t even save a quick test to the session inside the closure - this outputs immediately upon loading the site up with a browser so I’m pretty damn sure it’s the top of the execution stack. Nothing else outputs or writes to the logger output file to this point from the rest of the code base :

$app->add(
function($request, $response, $next) 
    {
        $session = $this->get('session');
        $session->start();
        $response = $next($request, $response);
        $session->set('authenticated', true);
        $session->save();
        echo('<pre>'.var_dump($session).'</pre>'); exit();
        return $response;    
    }
);

Here’s the debugger output to the browser :

/srv/www/workspace/infraweb/src/middleware.php:49:
object(Odan\Slim\Session\Session)[53]
    private 'adapter' => 
        object(Odan\Slim\Session\Adapter\PhpSessionAdapter)[52]

I’m addled at this juncture, I would think the above should have worked. The code base I’m working with was handling session execution just fine until I started with the Slim stuff. Now, it’s seemingly disappeared and haven’t a single clue what it is that I’m not implementing correctly.

The execution order is the same as the slim framework default: Load up the $settings array (require), instantiate the slim app with the settings array as a variable passed to the constructor. Create a $container variable from the $app-getContainer() method , then the php require statements to load the dependencies.php, middleware.php & routes.php ending with an $app->run() statement as the last line in the index.php file per the Slim framework.

FWIW, I’ve attempted a coupla different composer session packages with the same results. I made the executive decision to utilize yours - easier to understand, utilizes the latest stable php release.

I really don’t know how to even speculate on what’s happening, makes little to no sense to me at the moment…

Whiskey Tango Foxtrot?!?

Thanx in advance…

Looking at the ‘prisma’ project you’ve authored on github - more of a real-world example of sorts. Some may find it complex, but these are the type of projects I’d personally prefer to peruse. I get more out of them applying concepts to real world implementations, not a real big fan of “Hello World” type things - fully realizing they have an appropriate place in the bigger scheme of things regarding peoples personal preferences. I tend to ‘void warranties’ and then go fix 'em… :wink: :grin:

Donning wetsuit, ready for a deep dive into that code…

Thanx! :metal:

1 Like

The session library is just a wrapper for the native $_SESSION variable.
Instead of var_dumping the $session variable try to read the value explicit like this:

var_dump($session->get('authenticated'));