Auto logout with Angularjs based on idle user
Asked Answered
E

11

78

Is it possible to determine if a user is inactive and automatically log them out after say 10 minutes of inactivity using angularjs?

I was trying to avoid using jQuery, but I cannot find any tutorials or articles on how to do this in angularjs. Any help would be appreciated.

Envelop answered 3/10, 2013 at 20:8 Comment(4)
See paulirish.com/2009/jquery-idletimer-plugin and github.com/ehynds/jquery-idle-timeoutGeordie
@Geordie he wrote that he tries to avoid jQuery...Freberg
have you get any options for this Functionality?Succession
Possible duplicate of Angular session timeout and managementEzra
O
113

I wrote a module called Ng-Idle that may be useful to you in this situation. Here is the page which contains instructions and a demo.

Basically, it has a service that starts a timer for your idle duration that can be disrupted by user activity (events, such as clicking, scrolling, typing). You can also manually interrupt the timeout by calling a method on the service. If the timeout is not disrupted, then it counts down a warning where you could alert the user they are going to be logged out. If they do not respond after the warning countdown reaches 0, an event is broadcasted that your application can respond to. In your case, it could issue a request to kill their session and redirect to a login page.

Additionally, it has a keep-alive service that can ping some URL at an interval. This can be used by your app to keep a user's session alive while they are active. The idle service by default integrates with the keep-alive service, suspending the pinging if they become idle, and resuming it when they return.

All the info you need to get started is on the site with more details in the wiki. However, here's a snippet of config showing how to sign them out when they time out.

angular.module('demo', ['ngIdle'])
// omitted for brevity
.config(function(IdleProvider, KeepaliveProvider) {
  IdleProvider.idle(10*60); // 10 minutes idle
  IdleProvider.timeout(30); // after 30 seconds idle, time the user out
  KeepaliveProvider.interval(5*60); // 5 minute keep-alive ping
})
.run(function($rootScope) {
    $rootScope.$on('IdleTimeout', function() {
        // end their session and redirect to login
    });
});
Onepiece answered 30/1, 2014 at 13:23 Comment(24)
Hey, your approach was working perfectly for me until I had issues on mobile safari where timeouts are paused when the page goes into the background. I had to modify it to set an idle timestamp on every watch which is then updated on interrupt. Before every update on interrupt though I check that the idleTimeout hasn't expired which works around the Safari issue (doesn't auto logout but logs out on first touch / mouse / click).Bitthia
@BrianF Interesting. If you're willing and able, I'd be interested in a pull request with your changes, or at the very least an issue open with an example of the code with your changes.Onepiece
have a look at a sample I threw up on github - github.com/brianfoody/Angular/blob/master/src/idle.js. I don't use the countdown or keepAlive functionality so this was just a stripped down version but you should be able to see my fix using idleCutOffMomentBitthia
@BrianF Thanks for that. I realize you use a customized version so it may not matter, but I'll be adding this to an official release.Onepiece
Fair play, I may use it for other projects so that's great. Thanks for the module by the way, the event interrupt was a great idea compared to listening on every digest.Bitthia
Using this but can't get it to work with my HttpRequestInterceptorFactory. Basiclly I just send a event on every http get/post/delete : rootScope.$emit("updateSession"); and/or rootScope.$broadcast("updateSession"); to my $idleProvider.activeOn("updateSession"); It works the first time but not when Im doing another requset. The timer doesn't reset after that..Nashbar
@Nashbar currently, ng-idle only determines idle state based on user DOM events, like mouse move, click, touch, etc. It will not be reset by $http requests or events broadcasted by $scope/$rootScope. If you open an issue in the github repository with some code examples, I can look into the issue further and perhaps enhance the module.Onepiece
@HackedByChinese ok I went with my own solution with a broadcast in my httpinterceptor and a eventlistener that reset a $timeout or $intervall. Greate module but not for me at this point.Nashbar
@HackedByChinese Nice job!! One minor thing, though: Would it not be better not to use an ng-prefix for the module and $-prefix for the services (since it opens the door to future naming collisions with AngularJS)?Hillhouse
@HackedByChinese I came across your github page on google. It's cool you can specify which events cancel an idle time. What if I have a video player. Can I also check for if my video player is playing and not paused? It's an HTML5 video so I assume I could get something working with your moduleSandi
@Sandi I would have to see your implementation to give you more details, but one thing I might suggest is suspending the idle service while videos are playing, and resume it when they pause or stop.Onepiece
@HackedByChinese I figured it out, In my IdleWarn event, I do function(){ if(!player.paused){ Idle.watch(); return; } .....} which resets the idleSandi
@HackedByChinese how can I update the session time? I need a different time in the case the user is loggin on my system.Teerell
How do I configure ng-idle to timeout regardless of user non idleness if he doesn't hit a button to explicitly hit a button to come out of idleness?Laurentium
Great module and it seems to do the job for me! Please update this answer with the latest changes so it matches your wiki (e.g. warningDuration() now being called timeout()).Greenness
@Greenness Thanks. Updated.Onepiece
@HackedByChinese Thanks :) in your snippet, is this all you need to do to get it working? It didn't work for me until I added Idle.watch() in the .run() function, the IdleTimeout event wasn't firing at all until I did that. I saw the call to Idle.watch() on your github demo though so that's where I got it from.Greenness
I am trying to use your module. Do you have a link to instructions for creating a free standing angularjs app that uses your module? I am using angularjs 1.4.x. Simply hacking things from your module into an app is causing errors. It would be helpful to start with something that works on my pc, and then move to import it into other apps after that. Thank you.Herl
I am following the directions at hackedbychinese.github.io/ng-idle/#gettingstarted but they are leading to problems when I try to import them into an existing app. It would be easier to just start from scratch with a minimal angular app that has this module as its primary element.Herl
@Herl It would be more appropriate to start a new issue that details the problem you're having (error messages, etc.). In terms of a fresh demo, there shouldn't be much more to it than what's in the guide. There's also a sample in the actual source code: github.com/HackedByChinese/ng-idle/blob/develop/sample/…Onepiece
Thank you. I did post a separate question documenting the specific obstacles to installing your minimal example in a minimal node.js installation. Are you willing to help me resolve the error to get your minimal example running in node.js? Here is the link: #35445627Herl
I am also experimenting with other ways to put ngIdle into an app, but they keep producing error after error after error. If you were able to publish a working sample app that can be imported using git clone https : / / working-sample_url, it might be more efficient than downloading the sample app in my OP and pointing out how to make it work. I really want to use your library, but I am concerned I am going to have to spend a full week and many SO questions getting the hello world version to work on my devbox before i can import it into a working app for real use.Herl
@HackedByChinese - Is it possible to get the timeout options like idle seconds from backend file using $http REST call to backend? I am not able to do it as $http is not working in config.Lingwood
In the performance view which method will work better?? Watching last digest (examples below) or watching events???Myrta
B
25

View Demo which is using angularjs and see your's browser log

<!DOCTYPE html>
<html ng-app="Application_TimeOut">
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
</head>

<body>
</body>

<script>

var app = angular.module('Application_TimeOut', []);
app.run(function($rootScope, $timeout, $document) {    
    console.log('starting run');

    // Timeout timer value
    var TimeOutTimerValue = 5000;

    // Start a timeout
    var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
    var bodyElement = angular.element($document);

    /// Keyboard Events
    bodyElement.bind('keydown', function (e) { TimeOut_Resetter(e) });  
    bodyElement.bind('keyup', function (e) { TimeOut_Resetter(e) });    

    /// Mouse Events    
    bodyElement.bind('click', function (e) { TimeOut_Resetter(e) });
    bodyElement.bind('mousemove', function (e) { TimeOut_Resetter(e) });    
    bodyElement.bind('DOMMouseScroll', function (e) { TimeOut_Resetter(e) });
    bodyElement.bind('mousewheel', function (e) { TimeOut_Resetter(e) });   
    bodyElement.bind('mousedown', function (e) { TimeOut_Resetter(e) });        

    /// Touch Events
    bodyElement.bind('touchstart', function (e) { TimeOut_Resetter(e) });       
    bodyElement.bind('touchmove', function (e) { TimeOut_Resetter(e) });        

    /// Common Events
    bodyElement.bind('scroll', function (e) { TimeOut_Resetter(e) });       
    bodyElement.bind('focus', function (e) { TimeOut_Resetter(e) });    

    function LogoutByTimer()
    {
        console.log('Logout');

        ///////////////////////////////////////////////////
        /// redirect to another page(eg. Login.html) here
        ///////////////////////////////////////////////////
    }

    function TimeOut_Resetter(e)
    {
        console.log('' + e);

        /// Stop the pending timeout
        $timeout.cancel(TimeOut_Thread);

        /// Reset the timeout
        TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
    }

})
</script>

</html>

Below code is pure javascript version

<html>
    <head>
        <script type="text/javascript">         
            function logout(){
                console.log('Logout');
            }

            function onInactive(millisecond, callback){
                var wait = setTimeout(callback, millisecond);               
                document.onmousemove = 
                document.mousedown = 
                document.mouseup = 
                document.onkeydown = 
                document.onkeyup = 
                document.focus = function(){
                    clearTimeout(wait);
                    wait = setTimeout(callback, millisecond);                       
                };
            }           
        </script>
    </head> 
    <body onload="onInactive(5000, logout);"></body>
</html>

UPDATE

I updated my solution as @Tom suggestion.

<!DOCTYPE html>
<html ng-app="Application_TimeOut">
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
</head>

<body>
</body>

<script>
var app = angular.module('Application_TimeOut', []);
app.run(function($rootScope, $timeout, $document) {    
    console.log('starting run');

    // Timeout timer value
    var TimeOutTimerValue = 5000;

    // Start a timeout
    var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
    var bodyElement = angular.element($document);

    angular.forEach(['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus'], 
    function(EventName) {
         bodyElement.bind(EventName, function (e) { TimeOut_Resetter(e) });  
    });

    function LogoutByTimer(){
        console.log('Logout');
        ///////////////////////////////////////////////////
        /// redirect to another page(eg. Login.html) here
        ///////////////////////////////////////////////////
    }

    function TimeOut_Resetter(e){
        console.log(' ' + e);

        /// Stop the pending timeout
        $timeout.cancel(TimeOut_Thread);

        /// Reset the timeout
        TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
    }

})
</script>
</html>

Click here to see at Plunker for updated version

Bouffant answered 18/9, 2015 at 3:11 Comment(5)
A cool solution, could be shorter by doing looping over the event names: var eventNames = ['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus']; for (var i = 0; i < eventNames.length; i++) { bodyElement.bind(eventNames[i], TimeOut_Resetter); }Draco
@Draco thank you for your suggestion, I updated my answer as your suggestion.Bouffant
I know this is an old answer, but to anybody reading this, you should turn events off in the logout section. Right now (dec-2017) bind is deprecated so you should execute bodyElement.on(...) and inside LogoutByTimer execute bodyElement.off(...)Imparisyllabic
This is pretty cool. I added $uib.modal to give a warning to the user for the onlick.Punchboard
slightly modified your solution to my project specifics but it works! Thank you!Buckman
K
19

There should be different ways to do it and each approach should fit a particular application better than another. For most apps, you can simply just handle key or mouse events and enable/disable a logout timer appropriately. That said, on the top of my head, a "fancy" AngularJS-y solution is monitoring the digest loop, if none has been triggered for the last [specified duration] then logout. Something like this.

app.run(function($rootScope) {
  var lastDigestRun = new Date();
  $rootScope.$watch(function detectIdle() {
    var now = new Date();
    if (now - lastDigestRun > 10*60*60) {
       // logout here, like delete cookie, navigate to login ...
    }
    lastDigestRun = now;
  });
});
Kloman answered 3/10, 2013 at 21:24 Comment(6)
I think that this is a pretty novel solution and played around with it quite a bit. The main problem I have is that this will run on lots of other events that are possibly not user driven ($intervals, $timeouts, etc) and these events will reset your lastDigestRun.Armenia
You could use this method and instead of on every digest just check for certain events as the ngIdle module does below. i.e. $document.find('body').on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkIdleTimeout);Bitthia
I like the idea of the digest watch but if you have web sockets or something running you could always be active with that; hints defeating the point.Mangum
@BrianF Mmh will webSockets triggers events such as "mousemove", "keydown", "DOM-xxx" like those Brian says ? I guess, usually, this wouldn't be the case, would it ?Industry
"if (now - lastDigestRun > 10*60*60) {" does it mean that I have to wait 1 minute?Nurture
Correct me if I am wrong but.... isn't it true that $watch will not be able to detect events like mouse movement? If that is true.... I am finding it difficult to understand how one can use this as solution to detect user "idle" time.Haukom
D
11

Played with Boo's approach, however don't like the fact that user got kicked off only once another digest is run, which means user stays logged in until he tries to do something within the page, and then immediatelly kicked off.

I am trying to force the logoff using interval which checks every minute if last action time was more than 30 minutes ago. I hooked it on $routeChangeStart, but could be also hooked on $rootScope.$watch as in Boo's example.

app.run(function($rootScope, $location, $interval) {

    var lastDigestRun = Date.now();
    var idleCheck = $interval(function() {
        var now = Date.now();            
        if (now - lastDigestRun > 30*60*1000) {
           // logout
        }
    }, 60*1000);

    $rootScope.$on('$routeChangeStart', function(evt) {
        lastDigestRun = Date.now();  
    });
});
Declivitous answered 26/11, 2014 at 20:32 Comment(4)
Nice expample. On line 3 "var lastRun = Date.now();" I believe you meant the variable to be "lastDigestRun"Goshawk
For experiment sake, I changed it to time out after one minute of inactivity. And it keeps timing out while the user is in the middle of activity. what gives?Slattern
Using $rootScope.$watch in this case would require switching to setInterval, since $interval will trigger a digest on each function call, effectively resetting your lastDigestRun.Elizabethelizabethan
@Declivitous I am using your approach in my application ,but how could i clear the interval i am unable to stop by using clearInterval,This is the demo:github.com/MohamedSahir/UserSessionVitrescence
M
7

You could also accomplish using angular-activity-monitor in a more straight forward way than injecting multiple providers and it uses setInterval() (vs. angular's $interval) to avoid manually triggering a digest loop (which is important to prevent keeping items alive unintentionally).

Ultimately, you just subscribe to a few events that determine when a user is inactive or becoming close. So if you wanted to log out a user after 10 minutes of inactivity, you could use the following snippet:

angular.module('myModule', ['ActivityMonitor']);

MyController.$inject = ['ActivityMonitor'];
function MyController(ActivityMonitor) {
  // how long (in seconds) until user is considered inactive
  ActivityMonitor.options.inactive = 600;

  ActivityMonitor.on('inactive', function() {
    // user is considered inactive, logout etc.
  });

  ActivityMonitor.on('keepAlive', function() {
    // items to keep alive in the background while user is active
  });

  ActivityMonitor.on('warning', function() {
    // alert user when they're nearing inactivity
  });
}
Maupassant answered 12/1, 2016 at 4:13 Comment(3)
Please don't post identical answers to multiple questions. Post one good answer, then vote/flag to close the other questions as duplicates. If the question is not a duplicate, tailor your answers to the question.Marinamarinade
I am implementing this solution, pretty cool but I have doubt , is it tracking other browser activities too? I just want to restrict it my application meaning user is idle in my application then only auto logoutPyroclastic
@user1532976: scripts in a web application will not break out of the window (tab) it's in. So no, it won't track other activitiesLyophilize
A
3

I tried out Buu's approach and couldn't get it quite right due to the sheer number of events that trigger the digester to execute, including $interval and $timeout functions executing. This leaves the application in a state where it never be idle regardless of user input.

If you actually need to track user idle time I am not sure that there is a good angular approach. I would suggest that a better approach is represented by Witoldz here https://github.com/witoldsz/angular-http-auth. This approach will prompt the user to reauthenticate when an action is taken that requires their credentials. After the user has authenticated the previous failed request is reprocessed and the application continues on as if nothing happened.

This handles the concern that you might have of letting the user's session expire while they are active since even if their authentication expires they are still able to retain the application state and not lose any work.

If you have some kind of session on your client (cookies, tokens, etc) you could watch them as well and trigger your logout process if they expire.

app.run(['$interval', function($interval) {
  $interval(function() {
    if (/* session still exists */) {
    } else {
      // log out of client
    }
  }, 1000);
}]);

UPDATE: Here is a plunk that demonstrates the concern. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W. What this demonstates is that the digester run time is updated only when the interval ticks. Once the interval reaches it max count then the digester will no longer run.

Armenia answered 17/1, 2014 at 15:44 Comment(0)
C
3

ng-Idle looks like the way to go, but I could not figure out Brian F's modifications and wanted to timeout for a sleeping session too, also I had a pretty simple use case in mind. I pared it down to the code below. It hooks events to reset a timeout flag (lazily placed in $rootScope). It only detects the timeout has happened when the user returns (and triggers an event) but that's good enough for me. I could not get angular's $location to work here but again, using document.location.href gets the job done.

I stuck this in my app.js after the .config has run.

app.run(function($rootScope,$document) 
{
  var d = new Date();
  var n = d.getTime();  //n in ms

    $rootScope.idleEndTime = n+(20*60*1000); //set end time to 20 min from now
    $document.find('body').on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkAndResetIdle); //monitor events

    function checkAndResetIdle() //user did something
    {
      var d = new Date();
      var n = d.getTime();  //n in ms

        if (n>$rootScope.idleEndTime)
        {
            $document.find('body').off('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart'); //un-monitor events

            //$location.search('IntendedURL',$location.absUrl()).path('/login'); //terminate by sending to login page
            document.location.href = 'https://whatever.com/myapp/#/login';
            alert('Session ended due to inactivity');
        }
        else
        {
            $rootScope.idleEndTime = n+(20*60*1000); //reset end time
        }
    }
});
Centipede answered 5/4, 2014 at 15:57 Comment(0)
I
1

I think Buu's digest cycle watch is genius. Thanks for sharing. As others have noted $interval also causes the digest cycle to run. We could for the purpose of auto logging the user out use setInterval which will not cause a digest loop.

app.run(function($rootScope) {
    var lastDigestRun = new Date();
    setInterval(function () {
        var now = Date.now();
        if (now - lastDigestRun > 10 * 60 * 1000) {
          //logout
        }
    }, 60 * 1000);

    $rootScope.$watch(function() {
        lastDigestRun = new Date();
    });
});
Isogloss answered 24/9, 2015 at 7:3 Comment(2)
"10 * 60 * 1000" is it a number of miliseconds ?Nurture
In the performance view which method will work better?? Watching last digest or watching events???Myrta
B
1

I have used ng-idle for this and added a little logout and token null code and it is working fine, you can try this. Thanks @HackedByChinese for making such a nice module.

In IdleTimeout i have just deleted my session data and token.

Here is my code

$scope.$on('IdleTimeout', function () {
        closeModals();
        delete $window.sessionStorage.token;
        $state.go("login");
        $scope.timedout = $uibModal.open({
            templateUrl: 'timedout-dialog.html',
            windowClass: 'modal-danger'
        });
    });
Bonnice answered 3/3, 2017 at 4:34 Comment(0)
I
1

I would like to expand the answers to whoever might be using this in a bigger project, you could accidentally attach multiple event handlers and the program would behave weirdly.

To get rid of that, I used a singleton function exposed by a factory, from which you would call inactivityTimeoutFactory.switchTimeoutOn() and inactivityTimeoutFactory.switchTimeoutOff() in your angular application to respectively activate and deactivate the logout due to inactivity functionality.

This way you make sure you are only running a single instance of the event handlers, no matter how many times you try to activate the timeout procedure, making it easier to use in applications where the user might login from different routes.

Here is my code:

'use strict';

angular.module('YOURMODULENAME')
  .factory('inactivityTimeoutFactory', inactivityTimeoutFactory);

inactivityTimeoutFactory.$inject = ['$document', '$timeout', '$state'];

function inactivityTimeoutFactory($document, $timeout, $state)  {
  function InactivityTimeout () {
    // singleton
    if (InactivityTimeout.prototype._singletonInstance) {
      return InactivityTimeout.prototype._singletonInstance;
    }
    InactivityTimeout.prototype._singletonInstance = this;

    // Timeout timer value
    const timeToLogoutMs = 15*1000*60; //15 minutes
    const timeToWarnMs = 13*1000*60; //13 minutes

    // variables
    let warningTimer;
    let timeoutTimer;
    let isRunning;

    function switchOn () {
      if (!isRunning) {
        switchEventHandlers("on");
        startTimeout();
        isRunning = true;
      }
    }

    function switchOff()  {
      switchEventHandlers("off");
      cancelTimersAndCloseMessages();
      isRunning = false;
    }

    function resetTimeout() {
      cancelTimersAndCloseMessages();
      // reset timeout threads
      startTimeout();
    }

    function cancelTimersAndCloseMessages () {
      // stop any pending timeout
      $timeout.cancel(timeoutTimer);
      $timeout.cancel(warningTimer);
      // remember to close any messages
    }

    function startTimeout () {
      warningTimer = $timeout(processWarning, timeToWarnMs);
      timeoutTimer = $timeout(processLogout, timeToLogoutMs);
    }

    function processWarning() {
      // show warning using popup modules, toasters etc...
    }

    function processLogout() {
      // go to logout page. The state might differ from project to project
      $state.go('authentication.logout');
    }

    function switchEventHandlers(toNewStatus) {
      const body = angular.element($document);
      const trackedEventsList = [
        'keydown',
        'keyup',
        'click',
        'mousemove',
        'DOMMouseScroll',
        'mousewheel',
        'mousedown',
        'touchstart',
        'touchmove',
        'scroll',
        'focus'
      ];

      trackedEventsList.forEach((eventName) => {
        if (toNewStatus === 'off') {
          body.off(eventName, resetTimeout);
        } else if (toNewStatus === 'on') {
          body.on(eventName, resetTimeout);
        }
      });
    }

    // expose switch methods
    this.switchOff = switchOff;
    this.switchOn = switchOn;
  }

  return {
    switchTimeoutOn () {
      (new InactivityTimeout()).switchOn();
    },
    switchTimeoutOff () {
      (new InactivityTimeout()).switchOff();
    }
  };

}
Imparisyllabic answered 21/12, 2017 at 14:48 Comment(0)
A
0

[add below script in application reference js file ][1] [1]: https://rawgit.com/hackedbychinese/ng-idle/master/angular-idle.js

var mainApp = angular.module('mainApp', ['ngIdle']);
mainApp.config(function (IdleProvider, KeepaliveProvider) {
IdleProvider.idle(10*60); // 10 minutes idel user
IdleProvider.timeout(5);
KeepaliveProvider.interval(10);
});

mainApp
.controller('mainController', ['$scope', 'Idle', 'Keepalive', function ($scope, 
   Idle, Keepalive) {
     //when login then call below function
     Idle.watch();
     $scope.$on('IdleTimeout', function () {
         $scope.LogOut();
         //Logout function or redirect to logout url
      });
  });
Autobus answered 8/7, 2021 at 13:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.