Skip to content

Instantly share code, notes, and snippets.

@kdarty
Last active April 27, 2017 19:08
Show Gist options
  • Save kdarty/6204bef29354b7399a280e3892dc53de to your computer and use it in GitHub Desktop.
Save kdarty/6204bef29354b7399a280e3892dc53de to your computer and use it in GitHub Desktop.
Session Management and Extension from Web Front-end client with graceful user prompt.

Session Management and Extension

The one problem all web developers face is how to make Session Timeouts more user-friendly.

What do you do when a user decides to go do something else and doesn't finish what they are doing on your site? You know that there are Web Sessions and possibly even a Database Session that needs to be maintained. So how do you make that friendly to your user?

A good answer would be to prompt the user before timeout and give them the option to extend the Session.

If the user doesn't see or otherwise react to this prompt, you should simply log the user out and return to some sort of "home" or otherise "landing" page to make sure they don't come back only to find nothing works.

The following code can be tweaked to do just that.

How It Works

To start off, we need to seed the Session Timeout. In this case we are converting to seconds so however many minutes you have your Session Timeout set to, we will multiply that by 60 to break down the minutes.

But we can't just pop up a dialog when the user hits the timeout mark. So, we will handle that by setting up a "countdown" interval to watch for.

In the case of this demo, we will use 120 seconds or 2 minutes. You may wish to go with something shorter and can easily change that by seting countdownSeconds to whatever you want.

From the start we need to set sessionTimeoutSeconds to match the number of seconds that equals our Session Timeout. I would suggest doing this from the server-side as you can grab that value from what was set on the server but in the event you simply want to hard-code you can just set the value for sessionTimeoutSeconds and you will be good to go.

NOTE: In ASP.Net you can set the SessionState "timeout" in the Web.config and use that.

When the timeout period is met (Session Timeout - Countdown Time) we should display some sort of prompt.

As I typically use Bootstrap, I went with the Bootbox.js plugin but you can easily change this out to jQuery UI or whatever mechanism you want.

When and if the user opts to extend the Session we need a URL to call. For my implementation, I need to also maintain a Security Session with my Database so I am using a quick AJAX call that makes an inocuous Database call to extend the Session. You may instead opt to load an image from the server into a variable. This will work just as well.

An example of using an image would be something like this:

var img = new Image(1, 1);
img.src = extendSessionUrl;

You will definitely want to modify the refreshSession method to suit your needs.

Along with a method for refreshing the Session, you will also need to provide a URL for where to go if the user opts to Log Out or the Session times out.

var extendSessionUrl = '/Account/ExtendSession',
var expireSessionUrl = '/Account/LogOff';

This code was borrowed by and inspired by the following example:

https://fairwaytech.com/2012/01/handling-session-timeout-gracefully

<!-- Initialize Session Timeout -->
<!-- NOTE: This is actually a C# Razor View Snippet -->
<script type="text/javascript">
@{
@:var sessionTimeout = Number(@Session.Timeout);
}
</script>
<!-- Include our SessionManager Script -->
<!-- NOTE: This again is from a C# Razor View so ~/ means "from root" -->
<script src="~/Scripts/SessionManager.js"></script>
$(function() {
// HtmlHelpers Module
// Call by using HtmlHelpers.getQueryStringValue("myname");
var HtmlHelpers = function() {
return {
// Based on http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript
getQueryStringValue: function(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace( '/+/g' , ' '));
}
};
}();
// StringHelpers Module
// Call by using StringHelpers.padLeft("1", "000");
var StringHelpers = function() {
return {
// Pad string using padMask. string '1' with padMask '000' will produce '001'.
padLeft: function(string, padMask) {
string = '' + string;
return (padMask.substr(0, (padMask.length - string.length)) + string);
}
};
}();
// SessionManager Module
var SessionManager = function() {
// NOTE: I use @Session.Timeout here, which is Razor syntax, and I am pulling that value
// right from the ASP.NET MVC Session variable. Dangerous! Reckless! Awesome-sauce!
// You can just hard-code your timeout here if you feel like it. But I might cry.
var sessionTimeoutSeconds,
countdownSeconds = 120, // 2 Minutes
secondsBeforePrompt = sessionTimeoutSeconds - countdownSeconds,
displayCountdownIntervalId,
promptToExtendSessionTimeoutId,
originalTitle = document.title,
count = countdownSeconds,
extendSessionUrl = '/Account/ExtendSession',
expireSessionUrl = '/Account/LogOff';
var endSession = function() {
location.href = expireSessionUrl;
};
var displayCountdown = function() {
var countdown = function() {
var cd = new Date(count * 1000),
minutes = cd.getUTCMinutes(),
seconds = cd.getUTCSeconds(),
minutesDisplay = minutes === 1 ? '1 minute ' : minutes === 0 ? '' : minutes + ' minutes ',
secondsDisplay = seconds === 1 ? '1 second' : seconds + ' seconds',
cdDisplay = minutesDisplay + secondsDisplay;
document.title = 'Timeout in ' +
StringHelpers.padLeft(minutes, '00') + ':' +
StringHelpers.padLeft(seconds, '00');
$('#sm-countdown').html(cdDisplay);
if (count === 0) {
document.title = 'Session Expired';
endSession();
}
count--;
};
countdown();
displayCountdownIntervalId = window.setInterval(countdown, 1000);
};
var promptToExtendSession = function() {
bootbox.confirm({
title: "Session Timeout Warning",
message: "Your session is about to expire in <span id='sm-countdown' />.<br /><br />Do you want to extend the session?",
buttons: {
cancel: {
label: '<i class="fa fa-sign-out"></i> Log Out'
},
confirm: {
label: '<i class="fa fa-check"></i> Continue'
}
},
callback: function (result) {
console.log('This was logged in the callback: ' + result);
if (result === true) {
refreshSession(true);
document.title = originalTitle;
}
else {
endSession(false);
}
}
});
count = countdownSeconds;
displayCountdown();
};
var refreshSession = function(extendDatabaseSession) {
// Extend Client-side Session Timer
window.clearInterval(displayCountdownIntervalId);
var extendSession = (extendDatabaseSession != 'undefined') ? extendDatabaseSession : false;
// Extend Database Session (if requested)
if (extendSession) {
$.post(extendSessionUrl, function (data) {
if (data != 'undefined') {
if ((data === true) || (data === "true")) {
window.clearTimeout(promptToExtendSessionTimeoutId);
startSessionManager();
}
else {
endSession();
}
}
});
}
};
var startSessionManager = function() {
promptToExtendSessionTimeoutId =
window.setTimeout(promptToExtendSession, secondsBeforePrompt * 1000);
};
// Public Functions
return {
start: function(timeLimit) {
// Set Timeout based on Input if provided, otherwise default to 2 minutes
sessionTimeoutSeconds = (timeLimit != 'undefined') ? timeLimit * 60 : (2 * 60);
secondsBeforePrompt = sessionTimeoutSeconds - countdownSeconds;
startSessionManager();
},
extend: function(extendDatabaseSession) {
refreshSession(extendDatabaseSession);
}
};
}();
// Initialize Session Manager
SessionManager.start(sessionTimeout);
// Extend Session (client-side timer only)
// Optional: Remove This if you aren't doing AJAX Calls
$(document).ajaxStop(function () {
SessionManager.extend(false);
});
// Whenever an input changes, extend the session,
// since we know the user is interacting with the site.
// Optional: Only needed if you want to extend Session when user inputs data
$(':input').change(function() {
SessionManager.extend();
});
});
<system.web>
<!-- Set Session Timeout to 15 minutes -->
<sessionState timeout="15" />
</system.web>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment