[Solar-talk] Peristent logins Was: Re-introducing Solar_Session with adapter support

Antti Holvikari anttih at gmail.com
Sun Mar 2 15:24:06 CST 2008


I'm replying again to Leo's email and telling what changes I made to
the Session package in light of Leo's suggestions. I will explain a
final solution for the persistent logins problem at the end of this
email.

On Sun, Mar 2, 2008 at 12:29 AM, Leo Chiao <leo.chiao at gmail.com> wrote:
> Just a few things I noticed on your Solar_Session_Adapter_Sql.  When
> updating an existing record in _write, you aren't updating the datetime
> column that the garbage collector depends on.  This means that the session
> will never live longer than the default timespan.  I would suggest adding a
> modified_col which is used in both the _gc and _write functions.  When
> updating an existing session, modified_col would get updated and _gc would
> look at the modified_col rather than the created_col. You could just use one
> datetime column, but some people may want to know the create date and
> compare it to the modified date to see how long someone's session has been
> in use.
>
> Another issue, which is common people when implement database backed
> sessions is that the _read method should be periodically updating the
> modified_date.  In the scenario where someone is browsing a site with a
> session that is never writing/updating their existing session data (say
> someone browsing an online store, but never adding something to their cart),
> their session will get inadvertently garbage collected even when they are
> actively browsing the site.  The way PHP file based sessions work is that
> the file timestamp is updated when the file is read which most people
> overlook.  For performance issues, you probably wouldn't want to emulate
> that exact behavior and update on ever single read, but it is something to
> be aware of.

When I first sent a mail to this list a while back, someone mentioned
that he hasn't seen a session adapter that knows only to write session
data if it has changed. At thought this made sense so I wrote the SQL
session adapter so that it would only write when needed.

What Leo mentioned, made me realize that the *whole idea* of sessions
is to update the data every time and change the modified timestamp. As
Leo mentioned, the file based session handler modifies the file
timestamp. So this is the way *every* session handler should work.

I've now made the SQL adapter so that it updates the data in every
request at the same time modifying "updated_col". You could make your
own session adapter that doesn't write in every request. Though, you'd
need to come up with a way to "sometimes" update the data as otherwise
GC could end up clearing sessions that it should not yet clear. I
think it's not up to my SQL adapter do decide about those strategies.

> You may want to also add session_write_close() via a __destruct or using
> register_shutdown_function for the db backed sessions

I added a destructor that calls session_write_close(). This will make
sure session data gets written *before* PHP destroys $this. Thanks for
pointing this out.

>  On Sat, Mar 1, 2008 at 4:42 PM, Antti Holvikari <anttih at gmail.com> wrote:
> > Also, I think I've found a solution for the "remember me" feature
> > everybody wants to have. I've written a consept session adapter
> > (`S_S_Adapter_Remember`) that does exactly this. It uses a _setup()
> > hook which I've added to the abstract adapter class. In it, it checks
> > for a POST variable `remember`, and if it is set, it changes the
> > cookie lifetime to have a much bigger value (one week). A new cookie
> > will be sent with the new params if `Solar_Auth` sees a login attempt
> > and calls `session_regenerate_id()`.

Forget the above. This may sound too obvious, but persistent logins
(a.k.a "remember me") are not about sessions, they are about
re-authenticating users even when their session gets destroyed. In
reality this mean that your shopping cart could get emptied when your
session expires, *but* you could still be "remembered" and
re-authenticated with a special cookie.

We have been discussing a lot about this with Rodrigo. I was thinking
persistent logins could be accomplished with plain sessions, while
Rodrigo had his own auth adapter that sent a bunch of cookies and
re-authenticated users, keeping them logged in.

As it turned out, Rodrigo was right all along. You must have a special
auth adapter to accomplish truly persistent logins.

So, I refactored Rodrigo's auth adapter to only send one cookie and to
use Solar_Sql to store the few things it needs in order to work. The
result is `Lux_Auth_Adapter_Psql`[1] (P stands for persistent :-). It
extends the SQL auth adapter and adds some config keys among other
things.

It works like this:

1. If it sees a login attempt and user has clicked "remember me", it
will send a cookie with a user identifier and a secret token

2. Next time a user visits the page and his/her session has been
expired, the cookie is checked. If the cookie has appropriate info
(identifier matches a user, timeout has not been reached and the token
matches), the user gets authenticated again automagically. This logic
is taken from Shiflett's book "Essential PHP Security".

3. When user logs out, the cookie gets deleted.

In light of all this session-talk I'd like to give an example of a
complete session setup that has persistent logins, and auth
credentials and sessions stored in a database table. The beauty of
Solar is that you don't need to write a single line of code to
accomplish all of this. All you need to do is edit your config file
(ZF-guys can go home with their glue-code).

    // use the coolest auth adapter evah!
    $config['Solar_Auth']['adapter'] = 'Lux_Auth_Adapter_Psql';

    // don't expire valid auth status before
    // session is expired
    $config['Solar_Auth']['expire'] = 0;

    // don't set auth status to idle
    // before session expires
    $config['Solar_Auth']['idle'] = 0;

    // check this $_POST key to see if user wants to be remembered
    $config['Lux_Auth_Adapter_Psql']['source_persist'] = 'remember';

    // table name and columns for SQL auth storage
    // (some others are needed too but we'll use defaults here)
    $config['Lux_Auth_Adapter_Psql']['table']      = 'auth';
    $config['Lux_Auth_Adapter_Psql']['handle_col'] = 'handle';
    $config['Lux_Auth_Adapter_Psql']['passwd_col'] = 'password';

    // user will be remembered one week after each login
    $config['Lux_Auth_Adapter_Psql']['timeout'] = 60 * 60 * 24 * 7;

    // store all sessions in a DB table.
    // you need my session package for this.
    $config['Solar_Session']['adapter'] = 'Solar_Session_Adapter_Sql';

    // ini settings for sessions
    $config['Solar']['ini_set'] = array(
        // session is valid until the browser is closed.
        // this is the time your shopping cart will be
        // remembered.
        'session.cookie_lifetime' => 0,

        // session cookie is http-only
        'session.cookie_httponly' => true,

        // clear day old session data when GC gets called
        'session.gc_maxlifetime' => 60 * 60 * 24,

        // 1/1000 chance that GC gets called in a request
        'session.gc_probability' => 1,
        'session.gc_divisor'     => 1000,
    );

There, world peace.

[1]: http://code.google.com/p/lux/source/browse/trunk/Lux/Auth/Adapter/Psql.php

-- 
Antti Holvikari <http://anttih.com>


More information about the Solar-talk mailing list