How to Set Up Automatic Session Timeout with Ajax

  • google plus

In Brief...

If a user is idle for a certain amount of time, it is often a good idea to set up an automatic session timeout with Ajax to force a logout, especially if there might be sensitive data on the screen. This is normally handled on the server side; however, if we want to hide the data on the screen and alert the user that the session has ended, we'll need to handle the session on the client as well. One way to handle this is described below:

Take our Ajax Training course for free.

See the Course Outline and Register

Instructions

  1. When the user logs in, create a JavaScript timer with the window.setTimeout() method. The timer will call a function that ends the session after n minutes.
  2. Whenever there is user activity, the timer must be restarted.
  3. When the user explicitly logs out (e.g., clicks on a logout button), the timer must be killed.

For these examples to work, more files are required than are available in this how to. To see these examples in action, take the Ajax training course.

In this example, we'll look at a simple login form. The user logs in and is provided with a log out button. When the session times out, the page goes blank and an alert appears informing the user that the session has timed out.

Time Out

After the user clicks on the OK button, the login form reappears. Let's look at the code. There are quite a few steps involved in setting up an automatic session timeout with Ajax, as shown below.

  1. First, we start with the code sample:
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Authentication</title>
    <link href="Presidents.css" type="text/css" rel="stylesheet">
    <link href="Login.css" type="text/css" rel="stylesheet">
    <script type="text/javascript" src="lib.js"></script>
    <script type="text/javascript" src="inline-editing.js"></script>
    <script type="text/javascript">
    	var presConfig = {
    		userName: null,
    		autoLogoutTimer: null
    	}
    
    	function startApp() {
    		var loggedInDiv = document.getElementById("LoggedInDiv");
    		var logOutDiv = document.getElementById("LogoutDiv");
    		loggedInDiv.innerHTML = "Logged in as " + presConfig.userName;
    		loggedInDiv.style.display = "block";
    		logOutDiv.style.display = "block";
    		showPresidents();
    		startAutoLogoutTimer();
    		observeEvent(document.body, "click", startAutoLogoutTimer);
    	}
    
    	function endApp() {
    		var loggedInDiv = document.getElementById("LoggedInDiv");
    		var logOutDiv = document.getElementById("LogoutDiv");
    		clearTimeout(presConfig.autoLogoutTimer);
    		presConfig.userName = null;
    		loggedInDiv.innerHTML = "";
    		loggedInDiv.style.display = "none";
    		logOutDiv.style.display = "none";
    		loginForm();
    		unObserveEvent(document.body, "click", startAutoLogoutTimer);
    	}
    
    	function showPresidents() {
    		var xmlhttp = new XMLHttpRequest();
    		xmlhttp.open("GET", "Controller?req=Table", true);
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				results(xmlhttp);
    			}
    		}
    		xmlhttp.send(null);
    
    		function results(xmlhttp) {
    			var output = document.getElementById("Output");
    			output.innerHTML = xmlhttp.responseText;
    			enableEditing();
    		}
    	}
    
    	function loginForm() {
    		var xmlhttp = new XMLHttpRequest();
    		xmlhttp.open("GET", "Controller?req=LoginForm", true);
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				results(xmlhttp);
    			}
    		}
    		xmlhttp.send(null);
    
    		function results(xmlhttp) {
    			var output = document.getElementById("Output");
    			var loginForm;
    			output.innerHTML = xmlhttp.responseText;
    			loginForm = document.getElementById("LoginForm");
    			loginForm.onsubmit = function() {
    				login(loginForm.Username.value, loginForm.Password.value);
    				return false;
    			}
    		}
    	}
    
    	function login(un, pw) {
    		var params = "username=" + un + "&password=" + pw;
    		var xmlhttp = new XMLHttpRequest();
    		xmlhttp.open("POST", "Controller?req=Login", true);
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				results(xmlhttp);
    			}
    		}
    		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
    		xmlhttp.send(params);
    
    		function results(xmlhttp) {
    			if (xmlhttp.responseText.indexOf("failed") == -1) {
    				presConfig.userName = xmlhttp.responseText;
    				startApp();
    			} else {
    				var badLogin = document.getElementById("BadLogin");
    				var userName = document.getElementById("Username");
    				badLogin.style.display = "block";
    				userName.select();
    				userName.className = "Highlighted";
    				setTimeout(function() {
    					document.getElementById('BadLogin').style.display = 'none';
    				}, 5000);
    			}
    		}
    	}
    
    	function startAutoLogoutTimer() {
    		var sessionTime = 15 * 1000; //15 seconds
    		clearTimeout(presConfig.autoLogoutTimer);
    		presConfig.autoLogoutTimer = setTimeout(function() {
    			logout(true);
    		}, sessionTime);
    	}
    
    
    	function logout(auto) {
    		var xmlhttp = new XMLHttpRequest();
    		document.getElementById("Output").innerHTML = "";
    		xmlhttp.open("GET", "Controller?req=Logout", true);
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				results(xmlhttp);
    			}
    		}
    		xmlhttp.send(null);
    
    		function results(xmlhttp) {
    			endApp();
    			if (auto) {
    				alert("You have been logged out due to inactivity.");
    			}
    		}
    	}
    
    	function checkLogin() {
    		var xmlhttp = new XMLHttpRequest();
    		xmlhttp.open("GET", "Controller?req=LoggedIn", true);
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				results(xmlhttp);
    			}
    		}
    		xmlhttp.send(null);
    
    		function results(xmlhttp) {
    			if (xmlhttp.responseText.indexOf("failed") == -1) {
    				presConfig.userName = xmlhttp.responseText;
    				startApp();
    			} else {
    				loginForm();
    			}
    		}
    	}
    
    	observeEvent(window, "load", function() {
    		var btnLogout = document.getElementById("logout");
    		observeEvent(btnLogout, "click", function() {
    			logout(false);
    		});
    		checkLogin();
    	});
    </script>
    </head>
    <body>
    <div id="LogoutDiv">
    	<button id="logout">Logout</button>
    </div>
    <div id="LoggedInDiv"></div>
    <div id="Output">One moment please...</div>
    </body>
    </html>
  2. Now let's dissect the code so you can see how the automatic session timeout is created. This page contains the basic HTML elements that will hold data returned from the server:
    <div id="LogoutDiv">
    	<button id="logout">Logout</button>
    </div>
    <div id="LoggedInDiv"></div>
    <div id="Output">One moment please...</div>
    The "Output" div will hold the login form or the table data, depending on whether the user is logged in. This page also controls the flow of the application:
  3. When the page loads, the checkLogin() function is called, which looks like this:
    function checkLogin() {
    	var xmlhttp = new XMLHttpRequest();
    	xmlhttp.open("GET", "Controller?req=LoggedIn", true);
    	xmlhttp.onreadystatechange = function() {
    		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    			results(xmlhttp);
    	}
    	}
    	xmlhttp.send(null);
    
    	function results(xmlhttp) {
    		if (xmlhttp.responseText.indexOf("failed") == -1) {
    			presConfig.userName = xmlhttp.responseText;
    			startApp();
    		} else {
    			loginForm();
    		}
    	}
    }
    It makes an Ajax request to Controller, which handles the assignment of tasks based on the req parameter. In this case, the server-side code checks to see if the user is logged in: if so, it returns the user's name; if not, it returns the text "failed" if s/he is not logged in. The results() callback function then calls startApp() (if the user is logged in) to start the application or loginForm() (if the user is NOT logged in) to show the login form.
  4. Let's first see what happens when the user is NOT logged in:
    function loginForm() {
    	var xmlhttp = new XMLHttpRequest();
    	xmlhttp.open("GET", "Controller?req=LoginForm", true);
    	xmlhttp.onreadystatechange = function() {
    		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    			results(xmlhttp);
    		}
    	}
    	xmlhttp.send(null);
    
    	function results(xmlhttp) {
    		var output = document.getElementById("Output");
    		var loginForm;
    		output.innerHTML = xmlhttp.responseText;
    		loginForm = document.getElementById("LoginForm");
    		loginForm.onsubmit = function() {
    			login(loginForm.Username.value, loginForm.Password.value);
    			return false;
    		}
    	}
    }
    The loginForm() function uses Ajax to get an HTML login form and then, in the results() callback function displays the form and attaches the login() function to its submit event.
  5. When the user logs in, the login() function is called:
    function login(un, pw) {
    	var params = "username=" + un + "&password=" + pw;
    	var xmlhttp = new XMLHttpRequest();
    	xmlhttp.open("POST", "Controller?req=Login", true);
    	xmlhttp.onreadystatechange = function() {
    		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    			results(xmlhttp);
    		}
    	}
    	xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
    	xmlhttp.send(params);
    
    	function results(xmlhttp) {
    		if (xmlhttp.responseText.indexOf("failed") == -1) {
    			presConfig.userName = xmlhttp.responseText;
    			startApp();
    		} else {
    			var badLogin = document.getElementById("BadLogin");
    			var userName = document.getElementById("Username");
    			badLogin.style.display = "block";
    			userName.select();
    			userName.className = "Highlighted";
    			setTimeout(function() {
    				document.getElementById('BadLogin').style.display = 'none';
    			}, 5000);
    		}
    	}
    }
  6. The startApp() function does the following:
    1. Shows the logged-in message and the Logout button.
    2. Calls showPresidents(), which gets the editable Presidents table.
    3. Calls startAutoLogoutTimer(), which starts the inactivity timer. This function is recalled every time the user clicks the mouse. It is shown below:
      	function startAutoLogoutTimer() {
      		var sessionTime = 15 * 1000; //15 seconds
      		clearTimeout(presConfig.autoLogoutTimer);
      		presConfig.autoLogoutTimer = setTimeout(function() {
      			logout(true);
      		}, sessionTime);
      	}
    The startAutoLogoutTimer() function simply sets a timer to call logout() after 15 seconds and passing in true to indicate that it is an auto logout vs. a user-initiated logout. The function is recalled (and the timer restarted) each time the user clicks the mouse. We set the timer at 15 seconds so that you wouldn't have to wait too long to see it work. Normally, it would be much longer.
  7. The logout() function is shown below:
    	function logout(auto) {
    		var xmlhttp = new XMLHttpRequest();
    		document.getElementById("Output").innerHTML = "";
    		xmlhttp.open("GET", "Controller?req=Logout", true);
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				results(xmlhttp);
    			}
    		}
    		xmlhttp.send(null);
    
    		function results(xmlhttp) {
    			endApp();
    			if (auto) {
    				alert("You have been logged out due to inactivity.");
    			}
    		}
    	}
    }
    As we don't need a response from the server to log the user out, we use the "HEAD" method to send the request. The rest of the function sets the page back as it was when it first loaded.

Author: Chris Minnick

Chris is a prolific author and trainer, and the CEO of WatzThis?. His published books include Writing Computer Code, JavaScript for Kids, Coding with JavaScript For Dummies, Beginning HTML5 and CSS3 For Dummies, Webkit For Dummies, CIW eCommerce Certification Bible, and New Riders' XHTML.

Discuss