3

Update

After detailed investigation and consultation with some experts, it occurred to me, that idea of destroying sessions is an incorrect one. The better question would be — «How to force all users to log out».

And this problem should be solved not from the session perspective, which is a pretty low-level mechanism, but from the Security Component one. Even if you delete all session data, it will be re-created by means of remember me cookies with the next user requests.

I will try to present the valid solution to this problem later on.

The question

I need to implement a feature of so-called application «lockdown», so I need a way to log all users out of Symfony 2 application (close all active sessions).

What is the best way to achieve this functionality?

Ideally, the solution should be fully compatible with all possible save-handlers.

It looks like SessionHandlerInterface doesn't provide a method to do so.

Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202
  • 1
    You can do something like [this article](http://php-and-symfony.matthiasnoback.nl/2013/03/symfony2-security-enhancements-part-ii/) in the `Invalidate the Session Based on its Age ` section, based on a timestamp that you defined as expired. Hope this help – Matteo Mar 02 '15 at 09:26
  • A sound idea @Matteo! Looks like the best one so far, thank you ) – Slava Fomin II Mar 02 '15 at 09:37
  • 1
    I will post it as an answer so you can rate if you retain it useful – Matteo Mar 02 '15 at 09:38

3 Answers3

5

A Programmatic approach should be to use a session listener and invalidate the session if a particular event exist, something like a flag/timestamp alive in a database table or some similar.

As described in this article

Invalidate the Session Based on its Age

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class SessionListener
{

    /**
     * @var \Acme\DemoBundle\Service\SessionInvalidator
     */
    protected $sessionInvalidator;

    function __construct($sessionInvalidator)
    {
        $this->sessionInvalidator=$sessionInvalidator;
    }


    public function onKernelRequest(GetResponseEvent $event)
    {
        if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
            return;
        }

        $session = $event->getRequest()->getSession();
        $metadataBag = $session->getMetadataBag();

        $lastUsed = $metadataBag->getLastUsed();
        if ($lastUsed === null) {
            // the session was created just now
            return;
        }

        // "last used" is a Unix timestamp

        if (! $this->sessionInvalidator->checkTimestampIsValid($lastUsed))
         $session->invalidate();
    }
}

And the configuration:

<service id="amce_security.verify_session_listener"
         class="Acme\DemoBundle\EventListener\SessionListener">
<argument type="service" id="acme.session_invalidator"/>
    <tag name="kernel.event_listener"
         event="kernel.request"
         priority="100"
         method="onKernelRequest" />
</service>

Hope this help

Matteo
  • 37,680
  • 11
  • 100
  • 115
  • I've implemented this approach, however, call to a `$session->invalidate()` returns `false` for some reason and user is still logged in after that. Could you elaborate please? Maybe it's somehow related to listener priority and PDO initialization? I'm not sure. – Slava Fomin II Mar 02 '15 at 10:37
  • Which version of sf are you using? You can try invalidating the session with `$this->securityContext->setToken(null);` or maybe this code is executed to soon (i.e. the priority of your event listener is too high). – Matteo Mar 02 '15 at 10:42
  • Also http://stackoverflow.com/questions/18872721/symfony2-security-automatic-logout-after-an-inactive-period could be useful – Matteo Mar 02 '15 at 10:42
  • I'm using latest Symfony 2.6. I've created a Gist to show you my code: https://gist.github.com/slavafomin/960850217e40faaf4c99. I'm trying to use `$tokenStorage->setToken(null);`, but it's not helping either. User is still logged in after that. – Slava Fomin II Mar 02 '15 at 11:18
  • Even better, `$tokenStorage->getToken()` always returns `NULL` when I call it inside the listener. – Slava Fomin II Mar 02 '15 at 11:32
  • I look at [this](http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#deprecated-the-security-context-service) new features of sf2.6 that deprecate the `security.context ` service. I haven't any experience on this yet. I try to get time for investigate and try the new framework version. By the moment i can't help you so much.i am sorry :( – Matteo Mar 02 '15 at 12:01
  • 1
    It looks like they are separated one component into two, just a cosmetic change. Internally, it should work the same as before. I think these changes doesn't affect the behavior I'm encountering. Okay, no problem. Thank you for all your help! I will try to investigate this problem further on my own and will share details if possible. – Slava Fomin II Mar 02 '15 at 12:11
4

You could store the sessions into the database following this Symfony 2 Doc.

Basically you need to do the following in your config:

framework:
    session:
        # ...
        handler_id: session.handler.pdo

services:
    session.handler.pdo:
        class:     Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
        public:    false
        arguments:
            - "mysql:dbname=mydatabase"
            - { db_username: myuser, db_password: mypassword }

Then you could delete all sessions from the database!

KhorneHoly
  • 4,666
  • 6
  • 43
  • 75
  • Thank you for your reply. I'm using PDO save handler already and such an approach looks very tempting. However, this functionality will break, if later, we will decide to change save handler to e.g. `Memcache` or back to native implementation. Is there a better, polymorphic approach? – Slava Fomin II Mar 02 '15 at 09:08
  • 1
    The only other way I could think of would be to save all given session id's into a special database table, delete the session ids that logged out of your system and if you need to shut it down you just could just delete all session ids that are saved in the one table. – KhorneHoly Mar 02 '15 at 09:24
3

Ideally, the solution should be fully compatible with all possible save-handlers.

To be fully independent of the session save handler, I think it might be best if you implemented this “inside” of the sessions yourself.

When a user logs in, you store the current timestamp in their session (user-specific login timestamp).

When you want to apply your “lockdown”, then you store that current timestamp (lockdown timestamp) somewhere else, so that every script instance has access to it. That might be something like memcached/shared memory, a simple file (maybe using touch and filemtime), or something else.

Then on every page request, you check if the user login timestamp stored in the user session is greater than your lockdown timestamp. If not, the user logged in before the lockdown happened – then it is time to delete their session.
And of course you deny new logins while the lockdown is in place.

That way, you could even allow users to continue their existing sessions after the lockdown ends (if you want) – if you chose not to destroy their session, but simple deny access while in lockdown. For that, you either remove the lockdown timestamp completely (remove memcached/shmop entry, delete file), or set it to a value far in the past (f.e. 0).

You might want to implement an exception to the lockdown based on user level – an admin user should probably be able to still use the site (if only to disable the lockdown, if nothing else – and at least for that they need to be able to login regardless of the lockdown).

CBroe
  • 91,630
  • 14
  • 92
  • 150