Monday, June 9, 2014

Force your expired XPage to return to the login page

One of things that I have found annoying about XPages apps is that an application page left open in the browser for a long time will act like it is usable, but in fact the session has timed out and the page is unusable. When you try to actually submit anything the page will fail, and you will have to re-login.  I work for a bank now, where they are very concerned with security so I wanted my application to work similar to most financial websites.  In addition, I find a page you can't use a very poor user experience.  I had some time while the project is ramping up, so I decided to do something about it.

In this post, I will go over a simple way to force the page to redirect to the login page after the session expires.  I was actually surprised when I researched this that I couldn't find anyone who had already done it.  I put this Stack Overflow question in, and Stephan Wissel answered it and pointed me in the right direction.  He reinforced what I suspected that I would have to do this entirely in the client.

What does it do, how does it work

The concept here is pretty simple, when you load a page it sets a timer.  Whenever you submit anything to the server, or switch tabs in the application the timer is reset.  The timer accepts two parameters, the time to wait, and the page to redirect to.  The function does NOT log you out, that is what happens automatically by the server, this simply makes the client aware of what already happened.  In my case, I redirect to the application home page, so that after the login is re-entered it goes where I want it to.  It would be easy to just make the function refresh the current page.

var timeoutID; 
function setAutoRefresh(refreshTime, relativePath) { 
        if(timeoutID){ 
                window.clearTimeout(timeoutID); 
        } 
        timeoutID = window.setTimeout(function(){ location.replace(relativePath);}, refreshTime); 
} 


The same function will work for setting and resetting the timer.   Not sure why you would, but if you wanted to, you could have any number of timeouts on a page by giving them different variables.

You call the function like this:
var pathArray = window.location.pathname.split( '/' ); 
var path = "/" + pathArray[1] + "/" + pathArray[2]; 
//returns relative path to application home, example: "/atm/atm.nsf" 
//1860000 represents 31 minutes, one minute after session timeout 
setAutoRefresh(1860000, path); 

I made my timer set to one minute after the default 30 minute session logout.  The way this is written now, if the time was set less then it would redirect to the page and skip the login page which to me defeats the purpose. One warning, if you miss resetting the timer then the page could redirect unexpectedly.  In my application, I call the function from the onClientLoad of my layout custom control once and then in each button that perform updates. Note:  If you are not using a layout custom control, then you will have to put this in the onClientLoad of every XPage.  

Potential Enhancements

The code I wrote is pretty basic, maybe too basic.   Here are a two ways that you could make it more robust:
  • When the timer is up, you could create an ajax call to ping the server, and then redirect the page if you get returned a "Not Authorized" message.  If the session is still active you could reset the timer.  With this method, you could load it once, and just have it run every ten minutes, and never have to worry about it.  
  • Another enhancement, would be not reset the timer when you switch tabs or submit anything, but instead reset if based on a mouse moving event.  I considered this but thought it overkill for the application I am writing and didn't want to be calling the function non-stop. 
If anyone had any better ways of doing this, or anything to add, please comment.



August 2016 Edit:  


For some strange reason David Leedy is unable to comment on my blog so I am adding his comment here.

I'm adapting this code for my day job and also a future MWLug presentation and maybe even NotesIn9. I had problems with the code as it seemed to hardcode in the level of folder nesting. I ended up using this:
var thisUrl = window.location.href;
var lastSlash = thisUrl.lastIndexOf("/");
var dbPath = thisUrl.substring(0, lastSlash);
var finalPath = dbPath + "?Logout&redirectto=" + dbPath + "/sessionExpired?OpenPage";



console.log("dbPath : " + dbPath);
console.log("FinalPath : " + finalPath);

//1 minute = 60,000 milliseconds
setAutoRefresh(60000,finalPath); 

Thanks to both you Steve, and Daniel for this post and comment! 

2 comments:

  1. Thanks for this tip . . . I added to it and thought I would share. Instead of just reloading after 31 minutes to get the login window, I did this to actually log the user out:

    1) Set Anonymous access to 'NO ACCESS' but with Read Public Documents allowed
    2) Create a Page (not an XPage, just a page) and set the property on the page to 'Available to Public Access Users'
    3) Placed your code with the minor change in the path variable noted below.


    var timeoutID;
    function setAutoRefresh(refreshTime, relativePath) {
    if(timeoutID){
    window.clearTimeout(timeoutID);
    }
    timeoutID = window.setTimeout(function(){ location.replace(relativePath);}, refreshTime);
    }
    var pathArray = window.location.pathname.split( '/' );
    var path = "/" + pathArray[1] + "/" + pathArray[2] + "?Logout&redirectto=https://www.serveraddress.com/databasename.nsf/sessionExpired?OpenPage";
    //returns relative path to application home, example: "/atm/atm.nsf"
    //1860000 represents 31 minutes, one minute after session timeout
    setAutoRefresh(1860000,path);

    This will now redirect to a page called sessionExpired on which you can put whatever message you want *AND* it *WILL* log out the user. So you can have this set to a value less than what the server is configured to logout. Our server is set to 30 minutes, but for one particulare application I want them logged off after 15 minutes of inactivity. I can do that now by adjusting the time in the function call above.

    Sidenote: If you didn't want to set Anonymous to read public access documents, you could just create empty database with that ACL setting and put your page element in there and change the RedirectTo variable to that database.

    Thanks again!

    ReplyDelete
    Replies
    1. Daniel, Thanks so much for sharing! Your solution seems pretty solid.

      Delete