Session middleware

I made a Session middleware class (named “Session” and mostly borrowed from the internet).

<?php

namespace Custom\Middleware;

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

class Session {

     protected $options = array(
        'name'          => 'sessie',
        'lifetime'      => 3600,
        'path'          => '/',
        'domain'        => null,
        'secure'        => false,
        'httponly'      => true,
        'cache_limiter' => 'nocache',
        'autorefresh'   => false
    );
	
    public function __construct(array $options = array()) {
        if (is_string($options['lifetime'])) {
            $options['lifetime'] = strtotime($options['lifetime']) - time();
        }

        $this->options = array_merge($this->options, $options);
    }

    public function __invoke(Request $request, Response $response, callable $next) {
        $this->startSession();

        return $next($request, $response);
    }


    protected function startSession() {
        $options = $this->options;
        session_set_cookie_params($options['lifetime'], $options['path'], $options['domain'], $options['secure'], $options['httponly']);
        if (session_id()) {
            if ($options['autorefresh'] === true && isset($_COOKIE[$options['name']]) && ini_get('session.use_cookies')) {
                setcookie($options['name'], $_COOKIE[$options['name']], time() + $options['lifetime'], $options['path'], $options['domain'], $options['secure'], $options['httponly']);
            }
        }
        session_name($options['name']);
        session_cache_limiter(false);
        session_start();
    }

}

I also have a SessionHelper class (and file) with methods like ‘get’, ‘set’, ‘delete’ etc.

<?php

namespace Custom\Session;

class SessionHelper {

    public function get($key) {
        return $this->keyExists($key) ? $_SESSION[$key] : false;
    }

    public function set($key, $value, $overwrite = false) {
        if ($this->keyExists($key) === false || $overwrite === true) {
            $_SESSION[$key] = $value;
            return true;
        }
        return false;
    }

    public function delete($key) {
        if ($this->keyExists($key) === false) {
            unset($_SESSION[$key]);
            return true;
        }
        return false;
    }

    public static function id($new = false) {
        if ($new === true && session_id()) {
            session_regenerate_id(true);
        }
        return session_id() ? : false;
    }

    public static function destroy() {
        if (self::id()) {
            session_unset();
            session_destroy();
            session_write_close();
            if (ini_get('session.use_cookies')) {
                $params = session_get_cookie_params();
                setcookie(session_name(), '', time() - 4200, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
            }
        }
    }

    protected function keyExists($key) {
        return array_key_exists($key, $_SESSION);
    }

    public function __get($key) {
        return $this->get($key);
    }

    public function __set($key, $value) {
        $this->set($key, $value);
    }

    public function __unset($key) {
        $this->delete($key);
    }

    public function __isset($key) {
        return $this->exists($key);
    }

}

In my bootstrap (index.php) I have the following code:

<?php

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

require '../vendor/autoload.php';

$app = new \Slim\App(array(
    'debug' => true
        ));

$app->add($session = new Custom\Middleware\Session(array(
    'name'        => 'DevSession',
    'autorefresh' => true,
    'lifetime'    => '60 minutes'
)));

$container            = $app->getContainer();
$container['session'] = function () {
    return new Custom\Session\SessionHelper();
};

$app->get('/', function (Request $request, Response $response) {
	$this->session->set('time', time());
	echo $this->session->get('time');
    return $response;
});

// Run app
$app->run();

The if(session_id()) in the startSession() method of the class Session always returns false. But, the session is active in my $app though. I don’t really understand why the __invoke is called after each refresh of the page, the if(session_id()) returns false, but the session is still active. Can someone explain how this works?

The reason I’m asking is because I want to make a more secure PHP session class, with methods to check if the session has been hijacked for example. But if the session is never active, according to the Session middleware, I don’t know how to accomplish such a check.

Help?

Just so you know: the echo is for testing purposes only. This is to see if the session variable changes if I reload the page. It doesn’t, which is what I would expect. But nevertheless the if(session_id()) in my middleware class returns false after a reload of the page. I don’t get why.

Hi Scheper,

you check if session is active before starting session, that’s why it doesn’t work. You should remove session_id checking completely and use autorefresh checking AFTER starting your session.

protected function startSession()
{
    session_name($this->options['name']);
    session_set_cookie_params(
        $this->options['lifetime'],
        $this->options['path'],
        $this->options['domain'],
        $this->options['secure'],
        $this->options['httponly']
    );
    session_cache_limiter(false);
    session_start();
    if ($options['autorefresh'] === true) {
        setcookie(
            $this->options['name'],
            $_COOKIE[$this->options['name']],
            time() + $options['lifetime'],
            $this->options['path'],
            $this->options['domain'],
            $this->options['secure'],
            $this->options['httponly']
        );
    }
}
1 Like

But please help me understand something. After I invoke the Session class as middleware, session_start() is called. After I refresh the page there already is a session. I checked this by adding variables to the session from my bootstrap (index.php). How come the Session middleware doesn’t recognize there is an active session? I mean, session_start() was called and then I refreshed the page, so there already is an active session.

session_start() is also kind of configuration that tells your app that it will use sessions, for that reason session_start() has to be called before any session related actions and also before outputting anything to browser. App don’t have session id if you don’t call session_start() before trying to use that session id.

You can try this to load your page and set something in session, then remove session_start() completely and reload your page, you will see errors.

1 Like

Yes I think I understand, but my thougtprocess is as follows:

  1. I call my website: http://dev.local
  2. index.php is run, where my session middleware is called and my sessionhelper is placed in the container
  3. My session middleware calls session_start(), so a session is started (if not already)
  4. I reload http://dev.local
  5. I thought there would already be a session, because the session was previously started in step 3. Therefore I thought my 'if(session_id())" would return true instead of false.

But, if I understand what you are saying I’ll always have to call “session_start()” first, before checking anything that has to do with sessions?

Is it therefore wise to do a “session_start()” at the beginning of my custom startSession() method and then checking certain things? Like, if some variables are set. For example if the IP address in the session matches the IP address of the currect user?

Yes you need to call session_start on every page load before any session related actions. Session is created on first load, but you can’t access it’s content without calling session_start on every page load. You should set session name etc. before calling session_start, so you can’t start session in the beginning if you wish to have custom values.

1 Like

Thanks to your advice, I now have the following (incomlete) method:

protected function startSession() {
        $options = $this->options;
        session_set_cookie_params($options['lifetime'], $options['path'], $options['domain'], $options['secure'], $options['httponly']);

        session_name($options['name']);
        session_cache_limiter(false);
        session_start();

        if (session_id()) {
            if ($options['autorefresh'] === true && isset($_COOKIE[$options['name']]) && ini_get('session.use_cookies')) {
                setcookie($options['name'], $_COOKIE[$options['name']], time() + $options['lifetime'], $options['path'], $options['domain'], $options['secure'], $options['httponly']);
            }
        }
        
        //checking if session variables (like IP address) exist
    }

That’s better already, but

  • you don’t need to call session_id, because it’s always there after calling session_start
  • you don’t need to check if cookie exists, because it’s always there after calling session_start and seem to use session cookies anyway
  • same thing with ini_get(‘session.use_cookies’)) as you use cookies on other parts of your script anyway

I just yesterday pushed some code to properly handle session stuff to github. Initial testing seems to indicate that it works well (but it needs a little cleanup, which I’ll try to do tonight). You might want to take a look to see if this suits your purposes.

Thanks. I don’t really understand why your session middleware doesn’t really do anything. You just seem to be calling the session class in the container. I wonder why you’d use session middleware in this way. Would you explain this?

Plus, you seem to be making the same mistake I did. You check for a session_id() before session_start().

I don’t really understand why your session middleware doesn’t really do anything.

Yeah, it’s not done yet. At this point it just manages the session. I plan to add more services over the next few days. As for the structure, it basically does what https://github.com/akrabat/rka-slim-session-middleware does, it’s just that the logic which he placed in the middleware class has been placed into the main session class; I don’t see a problem using it this way. There is a reason for this and I will be expanding the docs over the next few days to explain the reasoning behind this.

Thanks for alerting me to the session_id() issue, I’ll look into this as soon as I find time.

1 Like

Here is my version of session middleware, got some ideas from codes above as well.

Secure session middleware for Slim 3 framework

  • longer and harder to guess session id:s
  • set session cookie path, domain and secure values automatically
  • extend session lifetime after each user activity
  • encrypt session data
  • helper class to set and get session values easily

Feel free to use this if it’s helpful.

1 Like

It really is helpful. This is a bit like I’d like to implement sessions in my framework. Although I also am building a handler for database storage of session data.

Hi @Scheper, this is very nice. However, allow me a few comments:

  1. I briefly looked at the code and have one pet peeve I’d like to mention. In SessionMiddleware::start() you use

    extract($this->settings);

Having been bitten in the past by this, I must say that I absolutely detest the use of extract(). The reason for this is that it imports variables into your main namespace and when accessing any variable, it is impossible to know whether it was expected to come from the extract() statement or whether it is a typo, leftover old code or anything else of that matter.

As such, in the interest of clarity, please consider removing extract() and replacing it by things like

$settings['lifetime']

If you’d accept it, I’d be willing to make a pull request for this.

  1. Furthermore, the session package I published relied on “joebengalen/config” to provide two very useful features:

a) A namespaced session container … while this might seem frivolous, speaking from experience, I can say that this can be a highly valuable and desirable feature.

b) The ability to deep-set array elements using the dot (.) notation. Again, nothing essential but a highly useful feature.

Please comment on my suggestions and let me know whether you’d be willing to add these features (or accept pull releases for them).

Please don’t misunderstand, I’m very grateful for your work, but IMHO it can be further improved to provide an even more useful session handler.

Hi TheLobos, and thanks for posting. That’s actually my package, not Schepers :slight_smile:

  1. Basically good point, but I don’t see that as a problem, as variables are only used as they are inside start() and start is not supposed to change those variables. But yes that can be done, basically code is just a bit cleaner the way it is now.

  2. Yes those are good features, but my package is meant to be simple, super fast and offer some good security features. I might add some similar features later.

By the way, none of these middleware session classes, which starts session at invoke, are not working with Slim-Csrf package (because Slim-Csrf validates session at construct), so I’ll write my own fix for that.

Thanks for posting!

Sorry @RikuS, I misread/misquoted (it’s getting late in my timezone :slight_smile:)

Let me know if you want me to help out with your package. I’m very interested to get a solid & secure session package for Slim and yours seems to be the the best one in terms of security so far, so I’d love to build on it.

No worries :slight_smile:

Also start() is using only settings variables, so my implementation should be pretty fine and clean.

I’ll test dotnotation some time soon as well, might be good feature to have. I’m hapy to have some new ideas, but still going to keep this small and fast, slim :slight_smile:

I’ll test dotnotation some time soon as well, might be good feature to have. I’m hapy to have some new ideas, but still going to keep this small and fast, slim :slight_smile:

All of those are highly appreciated. If you check the namespace implementation I referenced, you’ll see that it’s only a reference assignment at initialization; as such it should not impact performance in any signification way.

Once again, thanks for your work.

Yes sure namespacing doesn’t impact performance in any way, as it only adds extra level of array in session. I’ll set that up soon.

About dotnotation I’m not so sure yet, need to play with it first.