I have built an app in CakePHP that allows a user to login and do some stuff, it keeps the user logged in for about 24 hours I think by default. And this is handled by a session/cookie as a cookie also gets created...
1.) So what would a remember me bring to the party? As all that would do is create ANOTHER cookie that sets a timeout and keeps the user logged in... But this functionality exists in every single app by default with the session right? But I've seen lots of sites doing this but I don't get why as the session is doing this out of the box :/
2.) Also how come sessions expire even if a user continues to use a website? e.g. if I set it to be 1 minute but refresh every 30 seconds it will still expire... but I kept the site active before it could expire so how could it still did expire? This is annoying as I have a expiration for an app of 1 hour but even when the client is using the site it expires after 1 hour regardless of activity.
Would be great if someone could answer these 2 questions.
UPDATE: I've created a bounty on this in the hope of getting a CakePHP expert to help fix this problem. The issue is that the Sessions expire after the timeout REGARDLESS of user interaction. What I want to do is say I have a session lasting 5 minutes, and the user causes a postback every 30 seconds, then that session will still be around after the 5 minutes. This is not the case at the moment...
Configure::write('Session', array(
'start' => true,
'defaults' => 'php',
'timeout' => 1,
'cookieTimeout' => 1,
'autoRegenerate' => true
));
What it would bring is that if the user closes its browser and restarts it, it would still log in automatically. This is not the case with a session cookie, since such a cookie is deleted as soon as the browser is closed.
Maybe the page you went to every 30 seconds didn't start the session. In that case, the session mechanism is not used, and the expiration date of the session is not reset to now + 1 minute. Or maybe the refresh only hits the browser cache, and not the server.
OK, let's see if I can grab some of that bounty (booty?), while also testing my explanatory skills :)
So let's start with #1.
So what would a remember me bring to the party?
What's important to distinguish here is the difference between a "session cookie" and a "remember me cookie".
Since HTTP is a stateless protocol, a session cookie is used to tie several requests to a single user. Without it, every single request to your webserver is completely unrelated to every other request. Can you imagine writing applications without sessions? Every request is completely empty, no logins, no session variables..every request is an unknown user! This basically means no web applications!
Now, important thing here is to realise that you absolutely don't want your session to last 24 hours! In my book, this is a very big no-no. The shorter your session is, the safer it is (at least theoretically). Why? Because a session can be hijacked! The longer your session is around, the more chance it has of being hijacked.
For example, imagine a banking application. Also, imagine your user is accessing it on a public PC (our user is not the brightest). So he's managing his account or whatever..and his phone rings. Being an idiot, he takes the call and leaves, without logging out. Do you want your session to expire in 5 minutes, 15 minutes, or 24 hours? Don't know about you, but for something as critical as online banking, I want that session gone ASAP.
Moving on to the "remember me" part.
So session cookie "connects" multiple requests in a single session, what does the "remember me" cookie do? In simple terms: it ties multiple sessions to a single user.
You want your site to be easy and pleasant to use, and logging in is almost never pleasant. It's just an annoying thing you have to do every time before doing that thing you really want to do. A remember me cookie removes that annoyance.
You log in once, check the box, and now you're always logged in on that PC. This is why you should never use "remember me" feature while on a shared PC, because the next person will have your identity. Legitimately. This is why remember me cookies are also a security risk, they can be hijacked much like the session cookie.
Finally, there is one crucial difference between a session cookie and a remember me cookie: expiration. Session cookies normally expire when you close your browser (or after a time you've specified explicitly), whereas remember me cookies typically last for much longer.
Also how come sessions expire even if a user continues to use a
website?
To make it simple, they don't. You must have changed the way cake (or your application) handles sessions. The answer must be somewhere in your code. The reason why you didn't get a satisfactory answer here is because we can't see your code. You'll just have to debug and track what happens to your cookies. JB Nizet gave you some suggestions.
One thing I know that may cause trouble on some servers is cake's security level. Try lowering it in your /Config/core.php:
Configure::write('Security.level', 'medium'); // or 'low'
If that doesn't help, then the answer is definitely in your code. I hope this answer will push you in the right direction!
1.) The difference between a session cookie and "remember me" is that a session cookie has an expiration date of "0". Which means "expire when the browser closes". Whereas "remember me" gives a specific expiration date, say a month from now, to that same session cookie. That's the only difference. You might think this difference in functionality is trivial or meaningless, but consider this: at home I don't want to bother logging in every evening to the same damn Yahoo! account, whereas at work I don't want to bother deleting my cookies every time I have to go take a pee.
2.) Session cookies should not expire even when the application is being used. Where and under what circumstances are you seeing this behavior? It is wrong.
Understand the combination of the security settings combined with the timeouts, that's a very important part. When security is set at a high level sessions might be killed before you expect.
Next to that test with the internal cake session store. That way the local system configuration cannot influence it. It might be that PHP settings override the settings in Cake. So set the session store to Cake. That will create a directory with sessions which you can control.
Likewise it is a combination of server settings and expecting the config of Cake does override these server settings.
If I do this:
Configure::write('Session', array(
'start' => true,
'defaults' => 'cake',
'timeout' => 1,
'cookieTimeout' => 1,
'autoRegenerate' => true
));
/**
* The level of CakePHP security.
*/
Configure::write('Security.level', 'high');
It fixes the issue! So the session last 10 seconds, but If I refresh every 2-3 seconds then I will still be logged in after 10 seconds as the cookie is being refreshed. So it seems that the PHP settings of my hosting environment are/were causing the expiring session/cookie... Why would this happen though?
Related
How can I keep the user's session active, even if they accidentally closed their browser. Like in Facebook for example.
If you log in to their site and you close the tab or the browser, when you open a browser again and visits Facebook, they will automatically detect the active user and will not redirect you to the log in page.
How do I do that?
There's two relevant settings that control session's lifetime.
The first is session.cookie-lifetime. This is the lifetime of the cookie, which by default is 0, which means the cookie is destroyed when the browser is closed. You can set a longer lifetime by increasing this variable. It is relative to the server time, so you need to account for differences in the time in your clients' machine and your server's. Assuming they were the same, setting the option to i.e. 3600 would mean the session would expire in an hour. If you want to keep the session alive for a very long time, you increase this number.
However changing this value is not enough. There's also session.gc-maxlifetime, which is the time after which the session data is seen as garbage in the storage and is destroyed. This differs from session.cookie-lifetime because this option checks the last access time of the session data, so it is relative to the time the session data was last used (i.e. when the user was last active). Even if you set your session.cookie-lifetime to a high value, it'll not be enough because session.gc_maxlifetime is relatively low usually (1440 is the default, which is only 24 minutes).
While you can set these settings both to relatively high values and have it working, I would recommend against doing so, as this will leave a lot of unnecessary session data hanging around in your session storage, due to the GC not collecting actual dead session (which also increases the chance of someone hijacking a session in a system that is not properly secured). A better approach is making a remember me cookie. Basically you assign the user's ID and some authentication token that you store in the database for each user (this is to prevent someone spoofing the cookie) in the cookie, and give it a long lifetime. In your application's initialization code you'll check if the user is logged in. If he/she is not logged in, you'll check if the remember me cookie is set. If it is, you pull the user from the database based on the user ID in the cookie, and then validate the authentication token in the db is the same one as in the cookie. If they match, you simply create the session and log the user in automatically.
For anyone that come across this same issue, to keep the session cookie set for a long time is easy, on the login form, when you are creating the session for first time use this code, it will set the cookie time for a year (use your own time as its needed).
ini_set('session.cookie_lifetime', 60 * 60 * 24 * 365);
ini_set('session.gc-maxlifetime', 60 * 60 * 24 * 365);
session_start();
That should set the PHPSESSID cookie and your session will be safe... but is not the most secure way, so use it if you don't mind security issues
By default, PHP keeps a user's session open until their browser is closed. You can override that behaviour by changing the session.cookie-lifetime INI setting:
http://www.php.net/manual/en/session.configuration.php
However please see rekot post for a full answer
You should use cookies: http://php.net/manual/en/function.setcookie.php
Just store there some unique value that will help you identify the user.
Anyway, I strongly recommend you using some kind of framework, like CodeIgniter or Zend Framework, unless you're just learning how it works. It is easy to make critical mistakes in such a code and most frameworks are already well tested and safe to use.
Issue: Common sense would tell me that anything that actively interacts with the session would constitute session activity and would reset the inactivity timer that determines session timeout and destruction. That has not been my experience. I am trying to determine whether I have a fundamental misunderstanding in the way that sessions work or I have an esoteric bug in my code that is causing me to question the fundamentals of PHP sessions.
Context: I am implementing persistent logins for users of my web application. I've been trying to figure out what determines session inactivity/timeout for several days now, to no avail. I have scoured Stack Overflow and the internet at large (including most of the PHP documentation on sessions), and I still feel like I'm poking at this thing with a stick.
As far as I can tell, "Remember me" cookies, cross-referenced with database entries for user look-ups, are the preferred method of keeping users logged in to an application over periods of inactivity and the closing of their logged-in browser instance. I accept that, and I believe I know how to do it (there is a lot of good information online about the myriad approaches for this).
However, I initially tried to shortcut the moderate amount of work entailed in this approach by simply tweaking some session config parameters, namely: cookie_lifetime and gc_maxlifetime. I expected that increasing these parameters would increase the amount of time it took for the session to timeout, but that was not the case. I set both parameters to 1 year, and the session still timed out after about 40 minutes (which is also weird; everything I've discovered online indicates that 24 minutes is the default timeout).
Next, I tried to sidestep the inactivity thing altogether, and I added a "heartbeat" ajax request that pinged the server every 10 minutes, regenerating the session id and arbitrarily modifying a session key/value store. That experiment yielded the same 40 minute timeout result, with the ajax call consistently failing on its 4th call.
Here's some [simplified] code in case I'm looking in the wrong places for the source of my issue:
/* initialize_session.php (called at the beginning of every php file) */
$heartbeat_timer = 60 * 10; // 10 minutes.
if(session_id() == '' || !isset($_SESSION)){
# Some of these are already the defaults, but I'm being explicit to avoid ambiguity.
$session_params = array(
# Ensure that session file persists beyond heartbeat for keeping alive.
"gc_maxlifetime" => $heartbeat_timer + 60,
"cookie_lifetime" => 0,
"cookie_path" => "/",
"cookie_domain" => "shhhh.com",
"use_strict_mode" => TRUE,
"cookie_secure" => TRUE,
"cookie_httponly" => TRUE,
"use_cookies" => TRUE,
"use_only_cookies" => TRUE,
);
session_start($session_params);
}
/* any_page.php (html page where "inactivity" & timeout occur) */
<?php require_once __DIR__ . "/initialize_session.php"; ?>
<body>
blah blah blah
<script>
setInterval(function(){
$.ajax({
"url": "keep_alive.php",
"type": "POST",
"error": function(xhr, text_status, error_thrown){
// Log error stuff (incidentally, not helpful).
}
});
}, 600000/* 10 minutes in msec */);
</script>
</body>
/* keep_alive.php (arbitrary session modification to trick app into thinking I'm active) */
require_once __DIR__ . "/initialize_session.php";
session_regenerate_id(TRUE);
$_SESSION["last_heartbeat"] = time();
It may be worth noting that both the server file store for the session file and the client cookie for the session id are correctly updating per the regenerated session id every 10 minutes. The ajax interval just starts to fail on the 4th request (as I said, consistent with session inactivity timeout prior to all of the experimentation).
Allow me to propose a different approach to consider.
I wouldn't depend on PHP settings to get consistent results since even understanding how all those options work together is a daunting task.
I'd (on the server side, in your app) store the last activity timestamp together with the session ID (the one stored in the cookie). You can then use it to know if you still want to keep your user logged in or not. And you can update this entry in the heartbeat if you want.
Then you can set up PHP configs to have long timeouts for the session.
It would make it easier to debug, and later - if you wanted to implement a warning alert ("You'll be logged out in 10, 9, 8...") it would be easier as well.
From what everyone has contributed in the comments to my question, I am fairly certain there is a bug in my application and it just did a number on my understanding of session fundamentals. My assumptions regarding session inactivity and timeout appear to be correct. I'll edit my question and this answer once I get to the bottom of the issue.
I have developed this PHP web application which is now running for some months. Suddenly one of the users complained that he was able to login, but the session was terminated as soon as he clicked on any button! The same problem happened on different browsers.
After some tests I realized that a brand new session ID was created every time the user clicked on any button, probably because the original session was expired.
For whatever reason I took a glance at the user's computer clock and... surprise! His clock was 3 months in the future! I didn't know if such thing could have any relation to the failure, but I did fix the clock. Still it didn't work. I erased all cookies. Still nothing. So I restarted the browser - and then it started working again!
The closest information I got about this issue was Shimon Amit's answer to this question. Good, now I know that the clock "misconfiguration" is the cause. The problem is... I cannot keep every customer's computer clock under control. Some of them may have their computer clocks set in the future.
My question: is there any solution for this? Any trick? I don't want customers to face such errors as they may find it "lame" and break their trust on the application, even though it's not really my fault (in a sense).
Session cookies (like all cookies) are controlled and deleted when expired by the client browser. Even if you set a far out expire date (which you might not want to do anyhow) all the client needs to do is move his clock even farther forward and it will expire.
You can extend your session timeout to a later date. Perhaps you can use cookies that don't expire (sessions are related to cookies on the client side) Otherwise, your client's browser is just doing what it's designed to do.
EDIT: Javascript Option
This is a total hack, but you COULD use javascript to get the current time on the client machine and send it back to the server, then adjust the timeout on your session cookie to expire three months after that. See http://www.w3schools.com/jsref/jsref_gettime.asp
Once you have retrieved the client time, you can reset the session expiration using session_cache_expire(). http://www.php.net/manual/en/function.session-cache-expire.php
EDIT: 100% Server Side Option
Another option that I thought of would be to set a session cookie with no expiration, but track the time the cookie was set on the server, say in a MySQL table. You would also need to keep track of the last activity. Whenever a logged in user makes a request, you could check the start time of their session and their last activity. If the current time is greater than your acceptable timeout for either of these, then destroy the session server side and bring them back to the log in page. If the session is still ok, then update the last activity associated with that user so you can compare on the next request. No client side code necessary.
I fully agree with #MarcB's comment that you can't assume responsibility for how grossly misconfigured a user's machine could be. If you really want to make a difference in this regard I would suggest using PHP to output a small snippet of javascript that includes the time on the server. The snippet would compare that time to the time on the client computer and raise an alert if the time differs by more than X from the server. [say, 24hours or so]
Any trick?
Use session cookies. Not session in the meaning of PHP sessions, but browser session. Session cookies are stored until the user closes the browser. They are immune to whichever clock the user has set her computer. They will last until the browser is closed.
That is normally appropriate for PHP-session related cookies.
For PHP you need to ensure that the session cookie parameter lifetime is configured to 0 before the session starts. That is either the ini setting session.cookie_lifetime or by calling the session_set_cookie_params function.
For a more detailed description of cookie parameters, see the documentation of the setcookie function.
Second part of the trick is that you place a session start timestamp and a last activity timestamp into the PHP $_SESSION. Those are server based so have always the same base.
Check them, e.g. if the session is too old, last activity too long ago etc., destroy the session and force the user to login again.
You could even use that second part of the trick to combine it with a cookie that has it's expiry 10 years in the future (okay, browser might not like that, maybe you just want your three months).
Try to disable the session timeout or at least set it far into the future. That should do the trick.
I'm using Zend_Session to manage my user sessions, and I was looking to implement a "Remember Me" option in my application to keep users logged in for 2 weeks or so.
I've noticed that Zend_Session already has a built-in function called Zend_Session::rememberMe, however I'm not sure if that function logic is correct to use as a persisted login.
Essentially, the rememberMe function just extend the active session expiration date, which means if the user use the remember me option, he'll stayed logged in for 2 weeks with an active session.
This brings up 2 major issues.
I'm storing the sessions on the database, which means all these inactive users are stored for 2 weeks in my session table. I have over 50k inactive sessions, and it's hurting the application performance.
I want to know if a user came back to the site after 24 hours of inactivity, and revalidate his information. As his session remains open, I can't really tell if he came back after 1 hour or 1 week, as he has the same active session id.
I've read that if I want to implement a remember me feature, I shouldn't use the session cookie for that, and I should create another "login cookie" to remember a hashed user_id and a token. here's the complete explanation: What is the best way to implement "remember me" for a website?
So why does zend framework offers such a function, if using it can create performance and security issues?
One can only speculate their reasons for offering the function, but I don't see any major reasons against having it either. Plenty of programming languages give you facilities to do something bad or write code that has unseen negative side effects.
Sure, there can be unforeseen implications if someone arbitrarily sets it to a very large value, but the thing to note is that session data is still subject to garbage collection based on session.gc_maxlifetime regardless of the rememberMe time set on a cookie. Calling Zend_Session::rememberMe() has no effect on garbage collection for that data.
Consider the following:
Bootstrap.php
protected function __initSession() {
ini_set('session.gc_maxlifetime', 45); // set session max lifetime to 45 seconds
ini_set('session.gc_divisor', 1); // 100% chance of running GC
Zend_Session::start();
}
IndexController.php
public function indexAction() {
$data = new Zend_Session_Namespace('data');
if (!isset($data->time)) {
// no active session - set cookie lifetime and set some data
Zend_Session::rememberMe(90*86400); // 90 days
$data->time = time();
echo "Setting time";
} else {
echo date('r', $data->time);
}
}
If you were to access IndexController, the first time you would see Setting time. Then if you were to wait over 45 seconds, you would see the time printed out and (in my case) on the next request the session is expired. The session data is deleted from the server and although I still have the previous cookie, it is no longer recognized by the server.
I would expect that if you were implementing the garbage collection callback in your session save handler, then you should still see old session data removed from your database depending on what your gc_maxlifetime is set to.
To speak to your 2 questions:
As for your first issue, I would question why having 50,000 inactive sessions is hurting performance. If the database is properly indexed on the session ID, it should be extremely fast to get the session data even if there were millions of sessions in the database. Have you hit a hardware limitation possibly? Selecting data from 50,000 records when done properly should have little overhead.
In regards to your second issue, I agree with Mike, you should store a session value indicating when the last visit was, this way, when you start the session you can check their last visit and see how long it has been since their last page view. Then based on your threshold you can determine if they are returning to your site after being inactive.
For security reasons, if you detect it has been so long since their last visit, this is a good time to re-call rememberMe(), as doing so will issue a new cookie and help prevent session hijacking and fixation.
+1 for noting the major flaw behind Zend's approach to the 'remember me' functionality. Some people don't understand there is a penalty to be had when they attempt to extend the session lifetime, regardless of the session handler being file or db based. Allowing stale sessions to persist beyond a reasonable time-frame is a weak solution and you are better off implementing a custom cookie solution outlined by the link you provided.
The direct answer to your question; who knows. Maybe they didn't consider the fact that many users opt for database session handling, and figured piling up stale session cookies on the filesystem had no direct impact on performance.
Also, if you wanted to track if a user came back and re-established a stale session, you could add a 'updated_at' column to your session tracking table. So then you would have two timestamp columns; created_at and updated_at, which would help you make this determination.
How can I keep the user's session active, even if they accidentally closed their browser. Like in Facebook for example.
If you log in to their site and you close the tab or the browser, when you open a browser again and visits Facebook, they will automatically detect the active user and will not redirect you to the log in page.
How do I do that?
There's two relevant settings that control session's lifetime.
The first is session.cookie-lifetime. This is the lifetime of the cookie, which by default is 0, which means the cookie is destroyed when the browser is closed. You can set a longer lifetime by increasing this variable. It is relative to the server time, so you need to account for differences in the time in your clients' machine and your server's. Assuming they were the same, setting the option to i.e. 3600 would mean the session would expire in an hour. If you want to keep the session alive for a very long time, you increase this number.
However changing this value is not enough. There's also session.gc-maxlifetime, which is the time after which the session data is seen as garbage in the storage and is destroyed. This differs from session.cookie-lifetime because this option checks the last access time of the session data, so it is relative to the time the session data was last used (i.e. when the user was last active). Even if you set your session.cookie-lifetime to a high value, it'll not be enough because session.gc_maxlifetime is relatively low usually (1440 is the default, which is only 24 minutes).
While you can set these settings both to relatively high values and have it working, I would recommend against doing so, as this will leave a lot of unnecessary session data hanging around in your session storage, due to the GC not collecting actual dead session (which also increases the chance of someone hijacking a session in a system that is not properly secured). A better approach is making a remember me cookie. Basically you assign the user's ID and some authentication token that you store in the database for each user (this is to prevent someone spoofing the cookie) in the cookie, and give it a long lifetime. In your application's initialization code you'll check if the user is logged in. If he/she is not logged in, you'll check if the remember me cookie is set. If it is, you pull the user from the database based on the user ID in the cookie, and then validate the authentication token in the db is the same one as in the cookie. If they match, you simply create the session and log the user in automatically.
For anyone that come across this same issue, to keep the session cookie set for a long time is easy, on the login form, when you are creating the session for first time use this code, it will set the cookie time for a year (use your own time as its needed).
ini_set('session.cookie_lifetime', 60 * 60 * 24 * 365);
ini_set('session.gc-maxlifetime', 60 * 60 * 24 * 365);
session_start();
That should set the PHPSESSID cookie and your session will be safe... but is not the most secure way, so use it if you don't mind security issues
By default, PHP keeps a user's session open until their browser is closed. You can override that behaviour by changing the session.cookie-lifetime INI setting:
http://www.php.net/manual/en/session.configuration.php
However please see rekot post for a full answer
You should use cookies: http://php.net/manual/en/function.setcookie.php
Just store there some unique value that will help you identify the user.
Anyway, I strongly recommend you using some kind of framework, like CodeIgniter or Zend Framework, unless you're just learning how it works. It is easy to make critical mistakes in such a code and most frameworks are already well tested and safe to use.