Permanent logins with PHP sessions

16:09, Monday May 3rd, 2004 • feeling webmasterly

Disclaimer: I think this works, but it's only endured light testing so far. I may be wrong. Please let me know if you think I am.

PHP sessions are a great addition to the application programming environment. The first time I used cookies I used PHP's setcookie() function to roll my own system, which can only be described as flaky. Browsers seem to have an overwhelming disdain for cookies in general, each one requiring a complex set of chicken dances to work. PHP sessions may or may not do lots of clever stuff under the hood to address these problems, but they have always worked 100% reliably for me and that confidence has been enough for me to deploy and forget the technology, something which is critical if we are to get on with the business of developing actual software and not constantly writing our own libraries.

But eventually the time came when I wanted to have the option for people to be logged in permanently (or at least for a very long time, let's say 90 days). Finding a solution to this problem took me a little while, so I thought I'd write it up. I got quite a lot of help from this discussion at the Experts Exchange and obviously the sessions section of the PHP manual.

First piece of knowledge: PHP garbage collects session files when their modified time reaches a predefined timeout, the default being 1440 seconds - about 24 minutes. PHP has a series of INI file settings that govern the sessions system. These allow you, amongst other things, to control this garbage collection, by either raising the timeout, or reducing the probability of collection after that timeout has expired. For my purposes, I wanted the session files to remain intact on disk for up to 90 days (7776000 seconds). The php.ini key for garbage collection is session.gc_maxlifetime. If you have control over your php.ini file, simply locate and alter that value. If you don't, you can change these options via a .htaccess file (see below). It's not enough to change these options using the ini_set function as the value needs to be maintained for all instances of PHP that are working in the session dir.

According to one expert at the Experts Exchange, changing your session.gc_maxlifetime will cause problems when PHP instances running other scripts (e.g. belonging to others on a shared server). This can be fixed by moving the session save path to a different location. This can be acheived with session.save_path parameter.

Presumably, if you set your session.gc_maxlifetime and then move your session path with ini_set(), your sessions will be untouched by other PHP instances, meaning you can use just ini_set to do the initial lifetime change. I haven't tested this however.

I also decided to specify that my sessions should use cookies only. I set both session.use_cookies and session.use_only_cookies to "on" and session.use_trans_sid to "off".

As I don't have access to the php.ini on my production server, I put the following block into the .htaccess for my application:

<IfModule mod_php4.c>
    php_value session.gc_maxlifetime "7776000"
    php_value session.save_path "sessions"
    php_value session.use_cookies "on"
    php_value session.use_only_cookies "on"
    php_value session.use_trans_sid "off"
</IfModule>

OK, so now a user's session will be waiting on the server for them to come back, we have to ensure that the other half of the equation - the browser cookie - will wait just as long.

Second piece of knowledge: session_start() always sends a Set-Cookie header with the default path and no expiry time. No expiry time means that the cookie will be removed when the browser is closed. If session_start() sends a cookie with an expiry we don't want, don't we have something of a chicken egg situation? We need to run session_start() before we can access our session data, but to make the cookie optionally permanent you have to store that somebody wants a permanent cookie somewhere: the session. There are other options, but they are less then ideal. For example you could store the option in a database, but what if the user wants to be permanently logged in on their home machine, but not when they visit your site from a net café?

The solution is to stop and then restart the session with the new timeout parameter. The algorithm looks like this:

  1. Call session_start(), this sends a duff cookie, but it does give us access to $_SESSION
  2. Look in the session for the permanent flag, and copy it
  3. If the user wants a permanent login:
    1. Run session_write_close(), this commits the session to file and closes it
    2. Use session_set_cookie_params() with just one argument to set the cookie timeout, 7776000 seconds in our case.
    3. Run session_start again, this time it sends a good cookie

All you need to do when the user logs in is create the permanent flag in the session if they want it.

This solution has been tested and works on Mozilla, IE5/Mac, Safari, IE6/PC and IE5/PC.

Because two Set-Cookie headers are sent with each response, it's conceivable, even likely, that some browser somewhere will get confused and set the expiry time wrong. In this case it should be possible to do the work of the original session_start() call manually. Get the cookie using the $_COOKIE array, find the session file, parse the file for the perma flag (unserialize() didn't work as I hoped it might have done) and then resume the original process at step 3. I haven't implemented such a system myself as yet, so this approach is untested.

Mat says...

time: 17:14, Tuesday May 4th, 2004

tested OK so far for me in FireFox 0.8 Linux and Windows, IE5.5/Win, IE6/Win, Safari 1.0.1/Mac, IE5.2/Mac.

That's far from extensive testing, but it's showing promise :)

Suneel Kanuri says...

time: 22:42, Wednesday May 5th, 2004 • email: suneel AT newtekit DOT com

hi,

I found something interesting....potentially an other way around.

i am attaching an excrept from PHP.net website under description to session.save_path argument

"
There is an optional N argument to this directive that determines the number of directory levels your session files will be spread around in. For example, setting to '5;/tmp' may end up creating a session file and location like /tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If . In order to use N you must create all of these directories before use. A small shell script exists in ext/session to do this, it's called mod_files.sh. Also note that if N is used and greater than 0 then automatic garbage collection will not be performed, see a copy of php.ini for further information.
"

So i modified your setting of .htaccess so that, i will not specify the gc.maxlifetime and ty if it works.

would let you know soon
suneel

VCX says...

time: 11:11, Thursday May 13th, 2004 • email: VXCV AT FSFS DOT COM

FSDFSFSD

R. Rajesh Jeba Anbiah says...

time: 12:24, Thursday May 27th, 2004 • email: ng4rrjanbiah AT rediffmail DOT com

You're saying that alter the INI file or use .htaccess. But, I think, we can use ini_set() itself. But, if ini_set() is used, it must be set in all pages (probably can put up in a config.php file or so)

Tom Brown says...

time: 21:48, Monday June 28th, 2004 • email: tbbrown AT hotmail DOT com

when you choose to be logged in indefinitely, you logically bear the burden that you have also increased the risk that someone will guess your 128 bit session id and impersonate you. if you do not choose this, ideally, you should not have to bear this burden. unfortunately, the proposed solution seems to increase the risk for everyone regardless of their choice. this is because you have increased the timeout to 90 days for everyone and anyone can impersonate a cookie even if it has expired for the legitimate user.

Afternoon says...

time: 13:22, Tuesday June 29th, 2004 • email: noon AT aftnn DOT org

Thanks Tom, you're quite right to point out these potential security issues. If I can find a way to create the required functionality without introducing these risks that would be ideal, but at the moment I can't see around the problem.

Harshal says...

time: 8:21, Thursday July 1st, 2004 • email: machinerules AT yahoo DOT com

i run a site which has a session and certain session variables registered.
the site has some pages on "https".
so when i try to access the pages on "https" the session variables are lost.
can someone shed light on this

Sean says...

time: 9:59, Monday July 12th, 2004 • email: sean AT syosis DOT com

I personally use a custom session class that I coded which uses a single cookie ( holds the generated session id) and mysql.
So far it's done nothing but work perfectly for me, and has eliminated all the annoyances of php's built in session functions.
If you'd care to give the class a try, you're more than welcome to, just gimme a shout.

Ron Holland says...

time: 20:38, Thursday August 5th, 2004 • email: ron DOT holland AT vanderbilt DOT edu

I'm having trouble retrieving session data.
>The approved/correct method of registering variables in a PHP/Apache session is: $_SESSION['abc']="Hello World";
>Once registered, this assigned variable/value should be retrievable from a browser by calling.. echo or print $_SESSION[abc] ...displaying "Hello World."
>The value should be accessible across multiple pages using the same session.
>I have SuSe/Apache/PHP running on an x686 at home and can perform these actions across multiple browser pages without difficulty. The exact same code/actions will run on OS390 but only on the initial page from which they are called. On OS390 the session values cannot be retrieved when opening new browser pages. This only fails on the 390.
>I verified that the session data was being written to the server {Found in the "/tmp" folder)
>I verified the browser pages were calling the same session name and id
>I validated the code used to make the calls
>I verified all PHP settings in the "/etc/php.ini" file.

I'm including code (page.php and page2.php) that uses functions for registering values, naming & verifying sessions, and retrieves session data.

Any ideas on why a virtual environment might cause these functions not to work? ...or... Possible missing modules to enable same.


Matt Fletcher says...

time: 12:34, Monday September 27th, 2004 • email: matt AT 1stmorecambe DOT net

This article has saved me a lot of work, thank you very much.

Cedric says...

time: 11:54, Thursday October 14th, 2004 • email: cedric AT synix DOT net

Hi, the problem with the cookies stealing is still the same. If i take my cookie contening my phpsessid and i copy it on another computer, i can log on the website.
Is there a solution for permanent login on a website without any cookies ?
Thanks

Andrew says...

time: 21:35, Tuesday November 16th, 2004 • email: akomasinski AT gmail DOT com

ini_set and .htaccess will both be ineffective. The garbage collection routine happens before you get the chance to set them.. thus setting them even in every page, won't help. You have to alter it in php.ini itself if you really want to change it. I'm just going to use an iframe refresh and JavaScript (I have a controlled user set that needs to not get expired).

Fucker says...

time: 19:32, Thursday November 25th, 2004 • email: none AT yourmom DOT com

Your workaround is fucking sketchy as hell. Learn to code the right way prick!

Afternoon says...

time: 19:49, Thursday November 25th, 2004 • email: noon AT aftnn DOT org

Care to enlight me why? No, just a troll.

I know there are some problems with this code. I have now superceeded it with a class that inserts sessions in to a database. I would post the code, but it uses DB_DataObject, which renders it less useful that it could be for download. There are plenty of other ones out there though.

However, putting the session in the database is not ideal, it makes dealing with database outage a bit hairy for my liking. So I'll leave this post here for discussion and I thank people to be constructive and not trolls.

I Love You says...

time: 12:30, Wednesday August 10th, 2005 • email: mercedes AT arvind DOT com

"I Love You"

SG says...

time: 23:52, Tuesday December 20th, 2005 • email: sonika_1978 AT yahoo DOT com

Does anyone know how to create directories in windows when using session.save_path with N option(Suneel Kanuri has mentioned this option)?

Vanderzac says...

time: 5:48, Friday January 20th, 2006 • email: vanderzac AT yahoo DOT com

Somewhat (but not too much so) new to php here. I'm wondering if you could provide that link for the SQL setup, as I am trying to perm sessions on a shared server without access to the php.ini setting. So far I have all my sessions being written to a mySQL database, but every time the browser closes and re-opens, rather than opening the old session it just starts a new one. any help?

Michael B. says...

time: 17:20, Friday February 3rd, 2006 • email: mb AT tvrp DOT net

Ditto. You saved me much work and thought. Thank you very much.

Afternoon says...

time: 17:30, Friday February 3rd, 2006 • email: afternoon AT uk2 DOT net

It's great to be able to help out, even if just a little bit.

Personally I've moved over to Django to build web applications. It's Python based, but it solves a lot of the problems like this that are inherent to PHP. Spend an afternoon doing the tutorial and save yourself lots more time.

Permanent link

If you would like to link to this entry, it will always be available at http://aftnn.org/journal/508.

aftnn.orgafternoon's journal → entry 508