Tracking Online Users

Sometimes, the requirements for a web application include a list of users that are currently logged-in. It would seem, at first glance, to be pretty trivial, but because few of us explicitly log out of web applications, the reality can get complicated. Even the best solution is a trade-off. Dino explains the issues and alternatives.

Tracking Online Users

It’s not one of those features that just about any web site must have, but it’s not an uncommon requirement either. When you need a list of users that are currently online, you may wonder whether there’s a consolidated solution, an existing framework or function, or if you ought to craft your own solution. In the end, it’s the latter-do it yourself, but using some established practices. In this article, I’ll discuss a common way to track who’s online on a web site. As an example, I’ll be considering an ASP.NET MVC application with a login frontend that makes the site accessible only to authorized users.

What does ‘Online’ Mean to You?

In order to track online users, the first thing to do is to define exactly what is meant by an “online user”. I think we can go with the following definition without loss of generality. Users go online the moment they successfully log into the system. Therefore the number of online users is updated when a user successfully logs in into the system, whatever that means. Logging into a site that has a login frontend as part of the system would mean providing valid credentials that are successfully authenticated. Alternatively, it might just mean opening the home page (or a specific page) if you’re only interested in counting the current connections. As mentioned, I’ll be mostly covering the former meaning of logging in by authentication to work on the site.

Being Online in a Perfect World

In a perfect world in which users always behave predictably, it would be easy to count online users. You maintain a database of authorized users, add a column to track the online status and update it when the user successfully logs in or out. Here some sample code to illustrate the strategy.

The method MarkAsLoggedIn just takes the user name and looks into the Users table for a matching record. If it finds it, then it sets the column that indicates whether the user is currently online. In this example, I’m considering the validation of credentials and the update of the online status as two distinct operations. Needless to say, if you are in full control of the code that performs validation then you can check credentials and set the online status in a single database transaction. This is mostly a matter of optimizing things and adapting them to the business scenario; it doesn’t affect the overall strategy to detect online users.

Similarly, when the users log out through some user interface actions you first sign them out and then reset the online status.

In this scheme, it is fairly easy to provide admins with the list of online users. You just need to query the Users table for all users with a given value in a given column. Here’s an example:

So far so good; and especially easy. When are things starting to go bad? Well, first and foremost things get more complicated because users often just close the browser without first logging out.

Effects of Just Closing the Browser

When the user closes the browser, the current session ends. If the authentication cookie has a long duration it remains there and will automatically log the user back in on return. If the authentication cookie is associated with the session, then it will be found expired next time the user attempts to log in. is there anything we can infer from this information that helps determining when the user closes the browser and gets offline? Unfortunately, no.

You may think that the Session_End global application event may be helpful. Apparently, closing the browser window ends the current session so having a handler for the event may be a good trick to turn the user offline. First and foremost, it should be noted that Session_End only fires if session state is configured to be in-process. Even when this is the case however, Session_End is not fired if there’s no data previously saved in the session object. In other words, you cannot reliably use the event as the notification that the session has expired. Anyway, you can try placing an explicit sign out command in Session_End to programmatically log out the user.

However, that’s not enough for our purposes as the event never fires when the session ends because the user closes the browser window. To detect the closing of the browser window you can try using a bit of JavaScript. In particular, you can hook up the onbeforeunload event. Here’s an example:

Sounds just like what you need? Well, not exactly. There are a couple of issues to take into account. One problem is that whatever code you run should keep the script engaged waiting for it to return. Any asynchronous code risks being lost as the session may end before it completes. The other issue is even trickier. The onbeforeunload event is fired in several occasions and not just when the browser window is closed. It is also fired, for example, when a form that is contained in the page posts its content, when a tab is closed or when the same page is being reloaded for whatever reason, whether because the page is refreshed or reloaded programmatically. At the end of the day, it is too risky to rely on the JavaScript event.

What else can we use?

The Heartbeat Ajax Pattern

At some point when Ajax appeared, developers woke up to a new problem: Because most of the activity was taking place on the client, the browser session could easily expire without the user having sent any requests to the server. The Heartbeat design pattern was devised as a way of keeping the session alive on the server by periodically sending empty HTTP requests from the browser. Here’s a possible way to implement it in the Razor view of an ASP.NET MVC application.

The heartbeat method, called on the controller, may be implemented in such a way that it sets another field in the user record to track the last time that the user was detected to be alive and active on the application. For what it is worth, it should be noted that just by using the heartbeat pattern you will guarantee that the browser session never expires. This is not a bad thing per se as we’ve already seen that, without it, the use of Session is pointless when the purpose is detecting online users and their signouts. However, because it keeps the session alive it never frees browser resources and never stops pinging the server every specified number of seconds. It is a matter of tradeoff: the Heartbeat pattern can definitely help you detecting when users log out by closing their browser window, but it does that at the cost of sending a constant flow of small HTTP requests if the user doesn’t close the window. To make the deal a good deal, you might want to carefully define the frequency of the heartbeat; being aware that the greater the heartbeat interval, the less the impact on the system but the longer it takes for the system to detect any new disconnected users.

The implementation of the heartbeat in our case is fairly trivial and just consists in writing the current timestamp on the matching user record in the Users table.

Writing the heartbeat is not enough though. You might also periodically read it back and, from there, figure out whether the user is offline or actively connected to the site.

Reading Heartbeats

The idea is that once a user is authenticate on the site, the browser keeps on sending heartbeats at a known rate-typically every few seconds. If the user logs out, the script stops running. To implement this, you might want to wrap the script block presented earlier in an IF statement controlled by the condition that the request is authenticated. If not, then no script and no periodic heartbeat configured.

If the state of the user record in the database is read periodically, you can see if the user has been connected lately by comparing the current time with the last heartbeat stored in the database. In fact, if the timespan between the current time and the last read heartbeat is greater than the frequency of heartbeats then the chances are that the user closed the browser window. To make sure, you might want to check against twice or even three times the heartbeat frequency just to be sure.

If you heuristically determine that the user is disconnected, then you programmatically set the logged flag to false. The page of the site that is expected to display information about users currently online should incorporate some JavaScript code as below.

The code runs periodically and calls another server-side endpoint that gets the list of registered users and looks for those who are online. The online status is detected by looking at the timespan between the current time and last heartbeat. If any user is found with an expired heartbeat, and the status is currently set to ‘online’, then the status is automatically reset to offline.

How would you use such information to refresh the page? That depends on the technology, or just the programming approach you take to make your page auto-refreshable. You can have the controller method above just return a JSON list of users found to be currently online. In this case, it is assumed that you use an ad hoc data binding framework such as Knockout or Angular on the client side that knows how to bind JSON collections to HTML markup. You can return JSON regardless of the approach you use to connect, whether SignalR, as discussed in a previous article of mine on Simple-Talk, or plain handmade polling. As an alternative, you can return a chunk of HTML ready to be incorporated in the existing DOM page. This is precisely the approach illustrated in the example. The Online method on the Admin controller you see in the script code snippet does just that: it grabs data and builds a Razor partial view. Once done, it just flushes the markup to the output stream. On the client side, the HTML is received and promptly injected in the DOM for instantaneous refresh of the displayed page. The effect on the user is guaranteed: they just keep the page open and see online users come and go with just a few seconds of delay. The figure shows a screenshot taken from a real application where I used this technique.

2240-40f3e815-cd1d-4413-96dd-f77bd2447a0

As a bonus, you can also set the last-login date when you save the heartbeat and render it back to the admin users in charge of monitoring connections.

Summary

The only approach to monitoring connections to a page or site that works reasonably well is the Heartbeat pattern when adapted to notify a backend system about a running session. It is not free of issues though. The Heartbeat idea consists of placing a HTTP call every few seconds and, just because of that, there’s no chance that the session can expire naturally if the user leaves instead of closing the window or logging out. Because of this, the heartbeat approach can be sometimes heavy for the site, especially because the calls come from each potential client. Nothing is perfect in this world but everything is a tradeoff.