This ASP.net tutorial will demonstrate how to warn a logged in user if their login is about to expire by using an ASP.net UpdatePanel, Timer control and a ScriptManager. This functionality is handy for those secure sites you want to force your users to log in every time such as online banking sites.
What you will need:
- Visual Studio 2010 (2008 will work). I’m not sure if it will work with Web Developer Express, but feel free to try it.
- SQL Server or SQL Server Express (I’m using SQL Express for this demo)
- A SQL database enabled for ASP.net Application Services (by running aspnet_regsql.exe)
Adding a Login Timeout
In order for this to work you have to add a timeout setting to the forms setting in the FormsAuthentication section of the web.config.
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" timeout="30" slidingExpiration="true" protection="All"/>
</authentication>
The timeout is in minutes. Setting slidingExpiration=”true” means that any activity on the site renews the login session. If you have it set to false the login will timeout regardless if the user is doing something or not. This more than likely will not make your users very happy, so I’d leave it set to true if you ask me.
It’s also worth noting that you should disable the “Remember Me” option. You can’t log your user out after 30 minutes and also allow them to stay logged in. That makes no sense.
Set a Session Variable to Hold the Login Time
In order for us to know when the session is going to timeout we need to set a session variable to store the login time. We also will need to update that time when a user navigates to a different page or does any post-backs on the page they are on.
If you use the built-in .Net Login control you can add a method in the code-behind when a user logs in successfully. On your login page’s code-behind, add the following method:
protected void Login1_LoggedIn(object sender, EventArgs e)
{
Session["LoginStart"] = DateTime.Now;
}
The code assumes your login control is the default Login1.
If you’re using your own login form, simply add the above line directly before you set the AuthCookie, like so:
Session["LoginStart"] = DateTime.Now;
FormsAuthentication.SetAuthCookie(uname, cbRememberMe.Checked);
if (!String.IsNullOrEmpty(ReturnUrl)) { Response.Redirect(ReturnUrl); }
Running the Login Timer
There are a few ways to do this, but what I found works best in my situation is to create a base class for all my pages to inherit from. I call it BasePage
public class BasePage : System.Web.UI.Page
{
}
There are other benefits to this concept. For starters, you could one or more objects that almost every page in your site uses. Rather than creating the same code in every page you can put the logic in your base class. I actually create base classes for my pages, master pages, and user controls. Why not just add this to the master page? Well, you’d still have to write code to cast the master page to its type and then do stuff with it. That seems like a pain to me.
In the BasePage is where all the login timing and redirection will be handled.
First there are three global controls declared. Notice I’m using the AjaxControlToolkit so I added a ToolKitScriptManager. You can also simply use a regular ScriptManager.
[chsharp]Timer _t;
UpdatePanel _up;
AjaxControlToolkit.ToolkitScriptManager _tsm;[/csharp]Now we need a method to create the controls and add them to the page. Remember, this will be added to every page that inherits from this BaseClass.
private void CreateControls()
{
if (this.Master.FindControl("ToolkitScriptManager1") == null)
{
_tsm = new AjaxControlToolkit.ToolkitScriptManager();
_tsm.ID = "ToolkitScriptManager1";
this.Form.Controls.AddAt(0, _tsm);
}
else
{
_tsm = (AjaxControlToolkit.ToolkitScriptManager)this.Master.FindControl("ToolkitScriptManager1");
}
_t = new Timer();
_t.Interval = 1000;
//updating every second to display a clock.
_t.ID = "tmLoginTimer";
_t.Tick += Timer1_Tick;
//Add the Tick handler to the dynamic Timer control
this.Form.Controls.Add(_t);
//Goes in the form, not the page
_up = new UpdatePanel();
_up.ID = "upLoginTimer";
AsyncPostBackTrigger _trg = new AsyncPostBackTrigger();
_trg.ControlID = _t.ID;
_up.Triggers.Add(_trg);
//Add the Timer as an AsyncPostBackTrigger
this.Form.Controls.Add(_up);
//Again, the Form, not the Page
if (Request.IsAuthenticated)
{
_t.Enabled = true;
//Only when a user is logged in
}
else
{
_t.Enabled = false;
//Prevents the AsyncPostback when no one is logged in
}
}
The very first section of this code you may notice it looking for a ToolScriptManager on the page and if there isn’t one it creates it and adds it to the form. For some reason I couldn’t get this to work yet but I’m determined to so I’m leaving it here.
The Timer (_t) is created and set to tick every 1000 milliseconds (1 second). The ID is set to “tmLoginTimer”, this is required to set a trigger for the UpdatePanel.
The Tick event of the timer is set to execute the Timer1_Tick method. I’ll cover that a little later.
The Timer (_t) is then added to the Form–not the Page! If it’s not added to the Form it will not work.
Now the UpdatePanel is created with a new AsyncPostBackTrigger that is set to the Timer’s Tick event.
The final part of this is that if a user is logged in the Timer will be enabled. Otherwise it’s disabled.
Now for the Timer1_Tick method
protected void Timer1_Tick(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
return;
if ((Session["LoginStart"] != null))
{
DateTime dLoggedIn = Convert.ToDateTime(Session["LoginStart"]);
TimeSpan uTimeout = FormsAuthentication.Timeout;
TimeSpan tsExpired = DateTime.Now - dLoggedIn;
TimeSpan tsLeft = uTimeout - tsExpired;
//this is if you want to show a message when the login is about to expire
TimeSpan tsCutoff = TimeSpan.FromMinutes(2);
if (tsLeft < tsCutoff & tsLeft > TimeSpan.Zero)
{
//Show's a clock if the time left (tsLeft) is less than the Cut off time (tsCutoff)
//Need to make sure it is more than Zero or the timer will go negative and never
//log the user out.
HtmlGenericControl div = new HtmlGenericControl("div");
div.Attributes.Add("class", "loginTimer");
div.InnerHtml = tsLeft.ToString("m\\:ss");
_up.ContentTemplateContainer.Controls.Clear();
_up.ContentTemplateContainer.Controls.Add(div);
}
else if (tsLeft <= TimeSpan.Zero)
{
//Sign the user out, reset the session variable, set a session variable to diplay
//a "you have been logged out due to inactivity" message on the login page. Then redirect
//to the login page.
FormsAuthentication.SignOut();
Session["LoginStart"] = null;
Session["LoginTimeout"] = true;
//_t.Enabled = False
FormsAuthentication.RedirectToLoginPage();
}
}
}
First off, if there is no user logged in the method simply returns. If there is a user logged in the Session["LoginStart"] will update every second. You can set the cutoff (in minutes) if you want to display a message to the user that their time if about to expire. The example is set to 2 minutes. As soon as the timer hits 2 minutes a little box with the remaining time will display at the top of the page. You can of course use CSS to make your warning box look however you want. Here’s mine:
.loginTimer {
position: fixed;
top: 0px;
left: 0px;
padding: 4px;
background: #eee;
border: 2px solid #990000;
border-top: none;
border-left: none;
color: #990000;
font-size: .9em;
font-family: 'trebuchet ms',arial,helvetica,sans-serif;
font-weight: bold;
}
One More Thing
If you test this in the browser as it is, it works, but if you have any other UpdatePanels on your page you may notice that they also fire an AsyncPostBack with the Timer. The only thing you need to do to fix this issue is make sure all other UpdatePanels that are on your pages have the UpdateMode=”Conditional”. The default setting is “Always” which means the UpdatePanel will “post back” with all other UpdatePanels on the page.
Sample Code
Download the HandlingLoginTimeout Sample Project (Visual Studio 2010 C#).
