User Session Timeout

Home / User Session Timeout

When I’m online with my banking site, or Pandora, I occasionally get those nice dialogs asking if I’m still around after being idle for a while. Sometimes, I think it’s a nuisance, but it can be a helpful security measure. It can also be beneficial in SPA-type applications.

With SPA’s, the user can perform so many actions in the browser that never make requests of the server. As a result, if we’re using a cookie-based security mechanism that has a short expiration time, they could be effectively logged out and not even know it.

This is a problem.

A lot of developers will implement a ‘heartbeat’ request that causes cookies/session/security mechanisms to be refreshed.

To complicate matters though, the old .NET Membership provider doesn’t update the cookie unless the expiration time has hit the “half way mark” of its lifetime. What the what? For example, if we set our cookie timeout to have a sliding expiration of 15 minutes, the cookie expiration datetime isn’t refreshed until 7 minute 30 seconds have passed.

Angular makes these sort of things manageable. The components that we need include a timer service, a mechanism to reset the timer, and a dialog to let the user know their session is about to expire. Angular will be the glue that binds all of these mechanisms together in a cohesive manner.

Let’s take a look at what an idleService could look like. We’ll take a simple approach and use Agnular’s $timeout service to set/clear our interval. Alternatively, JavaScript’s native setInterval/clearInterval methods could be used.

var idleService = function ($rootScope, $timeout, $log) {
    var idleTimer = null,
        startTimer = function () {
            $log.log('Starting timer');
            idleTimer = $timeout(timerExpiring, 10000);
        stopTimer = function () {
            if (idleTimer) {
        resetTimer = function () {
        timerExpiring = function () {
            $log.log('Timer expiring ..');


    return {
        startTimer: startTimer,
        stopTimer: stopTimer,
        resetTimer: resetTimer

If we inject this idleService, it will kick off a 10-second timer. When 10 seconds is up, we get a message emitted (or you make need to broadcast it if you aren’t using $rootScope to listen) indicating that the user’s session is about to expire. In a real-world application, obviously, this would be a value that is 1-2 minutes less than your actual expiration time. For .NET Membership, I basically double the cookie timeout in my web.config (if I want the user to be alerted at 15 minutes, for example, I set the cookie timeout to 30). Then I take this value, divide by two and subtract a few minutes. By doing it this way, we ensure that our countdown timer expiration will not occur prior to cookie expiration and at any point in time if we are at the mid-point of the cookie life when a server request is made, the cookie will have been updated by the server. This takes care of the Membership provider cookie goofiness that I mentioned:

@((FormsAuthentication.Timeout.TotalMilliseconds / 2) - 120000)

With the idleService injected, all we get is a timeout message emitted. We have to determine when/how to reset the timer based on this message and/or server requests being made. There are two scenarios: automatically reset the timer since a server request was made incidentally, or reset the timer upon the user’s request.

The first scenario is pretty easy, if you’ll recall my other post on Angular request interceptors and displaying a loading modal. Basically, if the modal dialog is shown, we reset the timer. In our interceptor events where we are calling ‘turnOffModal,’ which checks if there are pending requests, we reset the timer:

if ($rootScope.showLoading) {

The second scenario is more involved since we want to display a dialog to the user to let them know their session is about to expire and provide them with the option to extend the session. What I like to do is put this code in one of my other services which I know is instantiated at application run-time. Putting it into a controller, since they are created and destroyed as needed, is not a good place. For the fiddle example, though, since I have only one controller in my demo, that’s where this bit of code is going:

var makeRequest = function () {
    // Make any general request to extend the auth cookie
        .success(function (data, status, headers, config) {
            if (data.success) {
            else {
    .error(function (data, status, headers, config) {
        // Could not make the request?

$rootScope.$on('sessionExpiring', function (event) {
    var modalTitle = 'Session Expiring!';
    var modalBody = 'Your session is about to expire.  Still there?';
    dialogService.openDialog("modalGeneral.html", ['$scope', '$modalInstance', function ($scope, $modalInstance) {
        $scope.modalHeader = $sce.trustAsHtml(modalTitle);
        $scope.modalBody = $sce.trustAsHtml($rootScope.stringFormat("<p><strong>{0}</strong></p>", modalBody));
        $scope.ok = function () {
        $scope.hasCancel = false;
        $scope.cancel = function () {

So, if we receive the ‘sessionExpiring’ message, we display the dialog to the user. If they click ‘Ok’ to extend the session, we fire off an $http request to an arbitrary end-point on our server. If accessing the endpoint is successful, we call idleService.resetTime(). Technically, with our requestInterceptor in place, it would also reset the timer, so our success handler may be redundant. I left it in place for illustration. However, if the request is not successful, we make the assumption that the user was not authenticated any longer to make the request, in this simple scenario, and just refresh the browser. Upon refreshing the browser, our server mechanisms for logging in should kick in and redirect the user to our login page.

The demo fiddle below illustrates everything minus the request interceptor / $http get. I didn’t want to bombard anyone’s site with heartbeat requests. 🙂

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.