1

I have a PHP application that stored the session ID of a user upon login in a MYSQL table, which is removed when the user logs out and their session is destroyed through the logout process. the User is not allowed to log in again if the MYSQL table still has a value in that session_id column. This works great in case of naturally triggered logouts. However when the user simply closes the browser window/tab, it seems that the server is retaining the session variables and moreover since the natural logout.php was not invoked the session_id column in the MySQL table is also not getting emptied out. AS a result, I can type in any of the URLs that are protected by the login from the same computer and get right in. This is a security issue - triggered by a user who did not follow the instructions of logging out naturally instead of just closing the browser window.

I have followed the SO thread here and have tried to use the onbeforeunload() JS function to handle this situation of a browser close. As you know the onbeforeunload() event gets triggered when any of these 4 events occur - (1) browser close (2) navigating away from the page to another page by clicking on a "" link (3) refreshing the page (4) submitting a form.

In the code below I am having the following problems in (1) and (3) and I am having a hard time figuring it out : Prob 1. When I close the browser window, I get that alert with "Stay On the Page " and "Leave Page" options. However that AJAX call to call_my_special_script.php is occurring on both options, when it should not occur in case of "Stay On The Page". call_my_special_script.php simply removes the contents of the session_id in the MySQL table. Prob 2. When I refresh the browser - Safari / IE the code says nothing should happen, but I am getting that alert show up when I am not expecting it. Regardless of my choice on the alert it is calling call_my_special_script.php and removing the session_id value.

Any ideas what I should be doing here to fix these problems?

<script>
 var validNavigation = false;

  function browserCloseEvents() {

  var dont_confirm_leave = 0;   
  var leave_message = 'You sure you want to leave the page?'
  function goodbye(e) {

 if (!validNavigation) {
  if (dont_confirm_leave!==1) {
    if(!e) e = window.event;
    e.cancelBubble = true;
    e.returnValue = leave_message;
    if (e.stopPropagation) {
      e.stopPropagation();
      e.preventDefault();
    }
    $.ajax({
        type: 'GET',
        url: "call_my_special_script.php",
        async: false,
        success: function () {
            alert("sucess");
        }
     });
    return leave_message;
     }   
   }
  }                                                                                                                          

  window.onbeforeunload = goodbye;
  $(document).bind('keypress', function(e) {
   if (e.keyCode == 116){
    validNavigation = true;
    }
   });

  $("a").bind("click", function() {
    validNavigation = true;
  });

$("form").bind("submit", function() {
  validNavigation = true;
});

 $("input[type=submit]").bind("click", function() {
  validNavigation = true;
 });

}   

$(document).ready(function() {
  browserCloseEvents();
});

Community
  • 1
  • 1
John G
  • 55
  • 1
  • 7

2 Answers2

0

As always, you should never base your application's security on some client side feature/function. Since they can(will) easily be bypassed.

In your case you can just make the session time out to 15 minutes and while the user is logged in just send an AJAX beacon request to reset that timer say every 5 minutes so, when the user is browsing your site then he will be logged in, if he closes the tab accidentally/intentionally, he will still be able to just reopen your site and still be logged in but only within 15 minutes of timespan. after which he shall have to re login, since his session is expired.

I've seen various CMS use this technique.

Also you can piggyback on other requests too like some AJAX call to check new messages ..etc. Like the StackOverflow does, maybe.

Mohd Abdul Mujib
  • 13,071
  • 8
  • 64
  • 88
  • Thank you very much for the insight. I do have a session inactivity timeout but that resets on every page on key or mouse events. without any such event being registered the session times out and everything is initialized. However that requires the browser to remain open. So I do need to track client side activity in order to decide the timeout. If the browser is closed the page timer cannot countdown and stays open. – John G Aug 07 '15 at 14:47
  • Umm... If I am not mistaken You are thinking of the *timeout* as in javascript, whereas the *timeout* that i mention of is in the user session stored on the server, with an expiry timestamp. So everytime you have to check if user is logged in, all you have to do is check if the the timeout time set on the session has passed. Something along the lines of -> http://thisinterestsme.com/expire-php-sessions/ – Mohd Abdul Mujib Aug 07 '15 at 19:28
0

What I wouldn't do, and why

In my opinion, you shouldn't rely on JavaScript at all in this case, for the following reasons:

  • JavaScript might be disabled in the first place.
  • It might take only a single JS error on the entire page to disable the "logout routine".
  • The browser might crash.
  • There is no way of reliably telling whether the user actually left your page or not.
    You'd have to consider a multitude of browser and operating system behaviours too.

    Examples:

    • If I open your page in two tabs and close only one of them, I still get logged out.
    • Hitting F5 on a Mac doesn't do anything (at least in Chrome). But if I do that on your page, it thinks I'm reloading it, and disabled the logout routine. I can then happily proceed to close the tab without being logged out.
    • Hitting CtrlR on Windows or CmdR on Mac does reload a page in Chrome, but your website would log me out when doing that.
    • Hitting Del navigates one page back in most browsers on Windows. The last page might or might not be on your website, but I'm getting logged out either way.

Also, I consider this very bad practice:

the User is not allowed to log in again if the MYSQL table still has a value in that session_id column.

I've seen people trying to justify this with "security reasons", but it really doesn't add anything.

If there was an attacker who stole your username and password, it would prevent them from logging in while you are logged in, but it would also prevent you from doing the same while they are logged in!
(Also if someone has your password, security has long been breached.)

And if they stole your session cookie, it wouldn't protect you at all.

The only thing it does is annoy you when you're logged in on device 1 and want to log in on device 2.

What I would do instead

  1. Let the user re-login at any time, overriding the old session.
  2. Associate the session in your database with an expiration timestamp.
    Once that timestamp has been crossed, the session cookie is no longer accepted and the user has to log in again
    Depending on what you want, you could either use something like "time of login + 2 hours", or "time of last seen + 15 minutes" (which you would have to update on every page load, ofc).
  3. Set no explicit expiration date for the session cookie, so according to PHP's setcookie function it should be deleted when the browser is closed.
    Some browsers might not do this, but that's what the expiration in your DB is for.
  4. (If you want to prevent session hijacking:)
    Associate the session in your database with the user's IP.
    If the session cookie matches, but the IP doesn't, the user has to log in again.

A (somewhat expensive) alternative

The easiest way of telling when a user "has gone away" is if you have an open connection, which closes when the user leaves.
This is not the case with HTTP(S) - it is based on subsequent requests.
This is the case, however, for WebSockets.

Now, the idea would be:

  • When the user loads the page, a WebSocket is opened.
  • Every 10 seconds (or so) the browser posts a message to the server.
  • As long as there is at least one WebSocket open for a user, and it has posted a message in the last 30 seconds (or so), the user stays logged in, otherwise their session is discarded.

For older browsers, you could add an AJAX fallback, which would also work, but generate a lot more traffic than WebSockets.

Upsides:

  • The window of opportunity for an attacker is drastically reduced.
  • Multiple tabs are no longer an issue.

Downsides:

  • Generates quite a bit of traffic.
  • Will log the user out when they lose their connection to the server, even when they don't close their browser.
Siguza
  • 21,155
  • 6
  • 52
  • 89
  • Thank you for your very detailed response @Siguza - I really appreciate the insight. – John G Aug 07 '15 at 14:31
  • 1) I have a protection for concurrent logins, so if a user is logged in, another one cannot. – John G Aug 07 '15 at 14:41
  • (2) I actually have an inactivity timeout in the app but that requires the user to stay in the browser in order to set a warning message 30 seconds before expiry When the browser window is closed of course with the client side not available. For a careless user, they may just close the browser, leave their computer and walk away, when a not-so-well-meaning person could directly type in a protected URL and get into the app, since the sessions are still active and the server or client side has no means of knowing that the original user is not there. – John G Aug 07 '15 at 14:42
  • (3) I tried setcookie() but Safari and IE is not responding to it. (4) More than session hijacking, the concern is the original user walks away from their computer and another "bad" user gets control of the open session by directly typing in a protected URL – John G Aug 07 '15 at 14:42
  • This must be a common requirement on all PHP applications - all the banks do this very well, I wonder how they detect browser closes only and cross-browser too – John G Aug 07 '15 at 14:48
  • @JohnG The bank where I have my money just logs me out after 10 minutes of inactivity. If I'm logged in in the online banking portal, walk away from my computer without locking it, and someone evil approaches within 10 minutes, then yes, they can steal all my money. A logout-JS won't help you there. I just had another idea, based on a persistent connection (see updated answer), but even that won't magically make the problem go away, it will only greatly reduce the risk of it happening. – Siguza Aug 07 '15 at 16:55
  • But for a user who leaves their browser open and their computer unprotected in an environment with people they don't trust, there is really nothing you can do at all. – Siguza Aug 07 '15 at 16:55
  • you are truly a scholar and your depth of knowledge is fascinating. Thank you for your additional insight. Never really considered Websockets but you have given me something to research on and it does look very promising. Thank you again – John G Aug 07 '15 at 17:44