PHP/MySQL Update database on session timeout - php

I'm working on a "flash website". In this program, I'd like to add an "Online Users" list, which relies on sessions: A session is started when a user logs in, and the user is marked as Online in the database. As soon as the logs out or closes the browser, the user is marked as Offline in the database.
I know that running some functions when the browser is closed will require Javascript, and it's not safe either: If the browser were to crash, the functions wouldn't run. That's why I've settled for the database updating if the user logs out or if the user's session times out.
I've been looking up session timeouts, and ran into this, along with many others like it marked as duplicates: How do I expire a PHP session after 30 minutes?
The problem with the answer's method is; It's a conditional sentence that checks if the user's last activity was X seconds ago, and if there was no activity, it times out. Useful in some websites, but useless for the Online Users list, since it updateds when a new request is sent, and since there won't be any requests after the user closes the browser.
Also mentioned in other answers is the use of session.gc_maxlifetime and session.cookie_lifetime, but the Best answer states that using them is a bad idea; One doesn't destroy the session, just the cookies, and the other is "cost-intensive".
What I want is the user to time out and the database to update and mark the user as Offline without using the If-Conditional sentence, or maybe with using a different If-conditional sentence that only has to be used once when the user logs in, like a timer or something, and whenever a request is sent, the timer restarts...those are just my ideas so far on how to solve this problem.
But, how do I do this? I'm sorry if the answer is something very simple and obvious, I'm very new to PHP.
Edit: Long Story Short:
I want to run a function after the user closes his browser, or is inactive for 20 minutes.
Clarification...
I want to update the database after 20 minutes of inactivity even if the browser has been closed.

I want to update the database after 20 minutes of the browser being
closed.
I don't think this is really possible to do with browsers being closed (reliably). If they click the logout button you can obviously run some code to mark the user as logged out. You can specify how long a session is good for with the functions you mentioned but all this does is makes the cookie be deleted on the client side, it doesn't trigger anything on the server side.
About the only thing you can do is track the time of the user's last activity and if they haven't had activity in a while assume they are gone. So just add a field to your user table and store a datetime or unix timestamp of their last activity. Then you can either run a cron job every few minutes that marks users as logged out or you can simply modify your SQL query to pull only users that have a last activity less than 20 minutes ago.

You can do this with the help of a online users table with last active field,
window.setInterval(function() {
$.ajax({
url: "http://yourdomain/updateLastactive/?uid=" + User_ID,
success: function(data) {
}
});
}, 5000); // 5 seconds
This page will update the current time-stamp as last active for that user
Then on online users query you can do like below,
SELECT UID FROM ONLINE_USERS WHERE LAST_ACTIVE > DATE_SUB(NOW(), INTERVAL 30 MINUTE));

if (!isset($_SESSION['CREATED'])) {
$_SESSION['CREATED'] = time();
} else if (time() - $_SESSION['CREATED'] > 1800) {
// session started more than 30 minutes ago
session_regenerate_id(true); // change session ID for the current session an invalidate old session ID
$_SESSION['CREATED'] = time(); // update creation time
}
Also read this
http://php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime

You want to catch the event by using the information here:
How to capture the browser window close event?
You will then need a page on the server that you call in that event handler with the sessionID.
That page calls a local script, page, or service that you set up to delay 20 minutes and expire the session. If you want to restart the clock if the user revisites before expiration, you will need to include a way to interrupt or cancel that script's process and you will need to retain the SessionID in a cookie which will need to be looked for upon every page load. If the cookie's SessionID is found and matches, the timeout gets interrupted/stopped (if it's still going). If it already completed, a new SessionID will need to be issued.
If you have a lot of these cancellations at the same time, you will instead queue them up on the server and have a process periodically check the queue and expire them as needed.
The way most sites avoid all this is to set a 30 minute or whatever session timeout that is enforced whether the visitor is active or not.

The easiest way to do this is to write a MySQL stored procedure that looks at the session timestamp and deletes the session data when it is > 20min old. You can also do it as an external cron job, but a stored procedure would be better.
In addition, you need to refresh the session timestamp every time the user connects.
If you want to make sure you don't delete a session when the user still has your 'page' open, then some background JavaScript refresh to your site will function as a keep-alive. Note that if the user closes the 'window' or 'tab', it will be the same as the whole browser being closed - I'm not sure you can do anything about this.
Of course, you to do this, you need to store your sessions in MySQL.
TL;DR
MySQL stored procedure or cron job deletes session if session_timestamp > 20min
Client-side Javascript does ajax refreshes to keep session open
Session timestamp updated every time the 'page' is requested (browser or ajax).

Related

How does Sleep Works in PHP for simultaneous requests from the same user[machine]?

I was trying to understand the way sleep works for the multiple request from the same machine.
For example, I want to track the user login and logout in one of my application and I have done as explained below:
An ajax request is sent to browser when an user tries close/refresh the current browser. What I have done is binded an event on page unload, which sends an ajax request on server, where I have set up the session timestamp value in session and used the sleep(10) as below ->
//sets the timestamp value
$session->timestamp = time();
//wait if another request arrives withing 10 seconds
sleep(10);
if ($session->timestamp) {
//clear user session here
$session->timestamp = null;
}
Since I only want to reset the session only when user is trying to close his browser tab/window, it works fine in this case and reset the session after 10 seconds.
But, I do not want to reset the session in case of page refresh. Since in the case of page refresh, Again the page unload event is called first which sets the timestamp value in session, but the request is being for refresh and it sets the timestamp value at very first place as in below code:
if ($session->timestamp) {
$session->timestamp = null;
}
Now what I am thinking is when 10 seconds complete in sleep call of previous request the timestamp value is set to null and therefore the user session should not be reset.
But somehow this is not the case. Might be I am missing something here.
If you want to destroy user session after he closes browser, just set cookie without expiring date.
You've only explained your problem in the comments - not in the question. Each http request is handled in isolation regardless if they are from the same machine, however when a client emits an http request, it may re-use an existing connection in which case the request will be blocked until the earlier request on the same socket is processed.
I need to track the login/logout activity of an user on my application
The right way to do this is in the session handler (and not embedding time information inside the session itself) or via SSO. You can set a very short TTL and use a javascript invoked beacon to get better granularity of the end-of-session time in the absence of an explicit logout.
Unless the user specifically logs out there wont be a logout event. Because of the way websites work you can't force the user to logout. Usually sessions just time out (30 minutes is a pretty common value). If a timely logout is imporant to you consider switching it around and implement a keep-alive ticker that will periodically (every 5 minutes?) send the server a message that the user is still logged in (= has the website open in a tab).
In the session table keep a timestamp of last notify, if that timestamp is too old you can consider the user has logged out. Run a scheduled job to record the log out 'event'.
Using onunload to perform an AJAX request is very unreliable because you can not force the browser to wait and you can not distguinish between a reload/redirect or a close.

How to handle browser close logouts in PHP?

I have a problem with logged in users closing their browsers.
My code can't run due to the browser closing and so their logonstatus cant update to 'N' in the database. Also due to the session being destroyed they cant go back to the main pages as I have this code if (!isset($_SESSION['logged in'])) { etc to prevent people from viewing any pages without logging in.
When a user logs on their logonstatus changes to 'Y' and I record the time they logged in.
I record their lastactivity time on each page load.
I redirect users to the login page and change their logonstatus if they have been idle for 20 min on a page.
I also have a cron job due to the browser close issue which runs every 5 minutes and checks if the users last activity has been longer than 20 min and if so their logonstatus becomes 'N'
I think users having to wait 20+ min to re-login due to browser close is too long and so I would like to make it possible to login in again straight away.
I have read about the unload functions of javascript but apparently it is unreliable.
Is there any other way I could go about this?
Closing the browser is always a client side action. So you will need javascript to send the action to the server for PHP to do something.
You can use onbeforeunload to send something to the server, but it is indeed unreliable. A more reliable method is to make the session time a lot shorter (eg: 2min) and then have an ajax call every 30seconds to the server to keep the session alive (make sure its a page with a very small impact on server/connection). If the request fails 4 times, the session is destroyed. Now your cronjob can run every 2mins and a user only has to wait that long.
Another approach is to store a cookie on the users computer with a GUID and save it in the database with the "Logged ='Y'". Now when somebody tries to log in to an account which is already logged in, check if its the same user (cookie) and if so, allow it.
This still makes it possible for one user to log in twice, just harder and not by mistake.
You need to change the duration of your session cookies so that they last as long as the browser window remains open; do this with session_set_cookie_params, setting the lifetime to 0. Don't forget to make sure that your cron script and PHP's session gc max lifetime don't delete sessions before 20 minutes have passed.
Since you keep a record of their last access time and check it on each request, you can continue to log out people after 20 minutes of inactivity (just destroy their session and redirect to the login page).

Check if user is offline

I have an online game. I wish to show how many user are online. The problem is to know when a user is offline.
Is there a way to perform a check on sessions cookie to acknowledge whether the session with the broswer was closed?
I was thinking about simply set a timeout on the server which launch a script that count how many session cookie are present, but how do I check if the session cookie is about somebody who's logged and not just a visitor?
How did you handle this?
1) I don't want to rely on a script fired with the logout button, since nobody ever logout... people simply close the browser.
2) About timestamps and registering activity? Since in my game users interact with an svg (not moving through pages), they generate a huge amount of clicks. Making a query for each click for each of them refreshing a record would be very expensive.
When the user interacts with the site, set their last activity time.
If it is longer than 30 mins or so, you can assume they are offline.
You can also explicitly set someone to offline when they click logout.
However, your case is a little different. You could use a heartbeat style script.
Whilst they are on the page, use setInterval() to extend the expiry date, up to a maximum range (in case the user leaves their browser window open for hours on end).
Since your code gets executed when the page is loaded you cannot make a check if the user closed his browser or not.
So the common approach would be to use timestamps and update this stamp if the user does something on your site and if the timestamp is older than say 5 minutes you just assume he is offline

Run query after session expire

I need a php code that will run a query after a specific session is going to expire,
if(!isset($_SESSION['test'])) query;
something like that but does work with session expire.
The problem you'll run into: a session doesn't just expire by itself, a session is expired on the next page load if the user waited too long. As there's no communication between browser and server between requests (user action), the server won't know if e.g. the user just closed the browser (or is doing the dishes while the website remains open).
So I guess there's nothing wrong with your code as it'll tell you if the sessin is expired on the next pageload (as long as there is a next pageload :)).
If you really need to make sure, the query runs after a session expired, I guess you'll have to save your sessions to a database and run a "cleanup"-script on each pageload to run your query and get rid of expired sessions. (e.g. save "lastUserAction" and compare that to whatever your session-limit is)
I would suggest to save the date you created the session in the database and check if the session expires like:
if($db->checkExpiredSession('test') == true) {
$db->query('...');
}
Just a thought.

Is my method of auto-redirect-login page correct in PHP?

I have a community site where a user can have many friends. When displaying all of his friends, I want to include whether his friend is online or offline.
My method is, when user logs in, create a session and update the users table, on status column "online". If he click the logout button, then i will set the status to "offline". What if he close his browser without clicking the logout button? Here is what I want to do:
session_start();
if (!isset($_SESSION['LAST_ACTIVITY'])) {
// initiate value
$_SESSION['LAST_ACTIVITY'] = time();
}
if (time() - $_SESSION['LAST_ACTIVITY'] > 3600) {
// last activity is more than 10 minutes ago
session_destroy();
//direct to a php, say this user is idle and thus status = offline
header("location: update_status.php?user=".$_SESSION['username']."&status=offline");
// den redirect them to login page
} else {
// update last activity timestamp
$_SESSION['LAST_ACTIVITY'] = time();
}
Is this an appropriate way?
EDIT:
It would be helpful to see some easy sample code with how to check whenever a user is online and update whenever a user visits a page?
Do I need to include php?user=$_SESSION['userid'] in every link?
Well if you determine whether or not a user is logged out, simply by your online/offline column - then users closing their browser window (without hitting your logout link/button) will still be logged in.
The normal approach here is to keep track of when your users move around your site, storing the last time of when they navigated to a page. Then you set a predefined constant of what makes a user active (say navigating around your page within the last 15 minutes) - and then you use this on your SQL Query to grab every user that is a friend of the visiting person and has been on the site within the last 15 minutes.
In a SQL Query where you store your time as datetime (this is a query for MySQL) this could look something like:
SELECT col1, col2, col3 FROM Users WHERE DATE_ADD(LastActive, INTERVAL 15 MINUTE) > NOW() AND UserIsFriendOfCurrentUser
Of course your query would need adapted to fit your setup better, but hopefully you get the idea.
In my Opinion this will not work. When the User closes the Browser, the code you put here will never be called.
A possible way would be to save the last time the user was active in the database, whenever the User calls a page. If the Users last Activity was longer than say 5 minutes ago, you could count him as offline, and show this to other Users.
I think that phpbb3 does it that way...
Getting an Event when the User closes the Browser is heavy and does not always work.
After the header location redirect, definitely put this line:
die();
Not all user agents (browsers, web spiders, etc) will listen to your redirect header. Killing the script is the only safe way to make sure they don't get the rest of the page.
As far as I understand your solution is the user still marked as online even when the last time the user was on the site is more then 10 minutes ago.
You should save in your database, the last time an user was active on your site. When a user looks then on the list of friends you get also this time and look if that is more then 10 minutes ago, i.e. the friend is inactive.
The best way to do this is to store activity times for logged in users. When they navigate to a page, or perform an action, update their "last activity" time in the database with the current time. If this time falls outside of a threshold (say, 15 minutes), then you can class the user as no longer active.
In our project we use this technique:
every user after login is being associated with session id.
browser sends an "activity-request" with AJAX by timer.
a lightweight server-side script updates last activity in DB, searching
user by session id.
cronjob updates all timed-out users, marking them as offline
(actually session id gets empty), and
executes logout routines for every
user to avoid corrupted data on
business-level layer.
What happens to user?
Until user closes his browser - activity is being updated. After closing the browser user gets timed out and marked as offline. When user tries to reach any location which requires authorization - the first thing we try to do is to find him in DB by session id, and if not - he just can see nothing but login form (i.e. include(); exit();).
Bad thing - tabbed browsers use the same session id for each opened tab in the same window. But our project is a game and multi-users from one browser are not allowed. Actually it's a very rare situation wher 2 or more users try to login from different tabs of one browser...
I see a couple of problems in your code:
if (time() - $_SESSION['LAST_ACTIVITY'] > 3600) {
// last activity is more than 10 minutes ago
session_destroy();
//direct to a php, say this user is idle and thus status = offline
header("location: update_status.php?user=".$_SESSION['username']."&status=offline");
}
In this piece of code you destroy the session and then get the username from the session you just destroyed.
By the way, if I understand your question, you don't get the intended behaviour with this code.
You check if the user is just accessing your site (owner of the session you start) has been idle form more than 10 minutes and, if true, you disconnect him just now that it tries to reenter in the site.
You shouldn't use $_SESSION for storing the last activity time. Every time a user goes to a page, update a column called last_activity with the current time in the users table:
<?php
session_start();
$userId = (int)$_SESSION['user']; // make sure it's an integer
$db->exec('
UPDATE users
SET last_activity = NOW()
WHERE id = ' . $_SESSION['id']
);
//...
When you retrieve your list of friends, add an if statement to check if they are online (this is the MySQL version):
SELECT
u.user_name, u.name, etc,
IF(
UNIX_TIMESTAMP() - UNIX_TIMESTAMP(u.last_activity) < 300,
1, 0
) as online
...
The 300 is seconds so 5 minutes. Adjust this as you see fit. Then your friends can be accessed like so:
$friends = $db->query(/* above SQL */);
foreach($friends as $friend)
{
if($friend['online'] == 1)
// online specific stuff
else
// offline specific stuff
}
Database sessions may be a good way for you to manage who is online and who isn't. Theoretically they should expire on their own. I wrote a how-to on my blog about it, you can read more at http://brennydoogles.wordpress.com/2011/09/06/taking-control-of-your-php-sessions-using-database-sessions/
This also has the added bonus of allowing you to kill a user session remotely if you need to :)

Categories