Forms Authentication – How To Handle Simultaneous Users

I've seen many different approaches to handling simultaneous users within an ASP.Net application. I don't see why people try to create it themselves when there is a built-in ASP.Net solution. Let me explain.

Some solutions require you to record data in your database which you check against whenever a request is made. Other people suggest you set a date/time variable in the session and, once again, check against that data whenever requests are made. These ideas are good, but when ASP.Net has a mechanism that already checks against session variables, why would you re-create the wheel?

At it's simplest, we will be discussing the following components of the .Net runtime.

  • ASP.Net Membership Provider
  • ASP.Net Forms Authentication

Firstly, we will configure our application to use Forms-based Authentication. Do this in the web.config file in the root of your web application. NB - you can put your configuration code directly in the system.web section, or you can link off to a separate file. Linking to a separate file can be useful when your web.config is large or if you just want to make it easier to find your authentication code. I have shown an example of the linked code below, but I will omit that in later code examples. Just know that you can use either option.

<system.web>
	<authentication mode="Forms">
		<forms loginUrl="~/login.aspx" timeout="5" slidingExpiration="true" />
	</authentication>
	<!-- 	*** Or you can use a linked file using the below line. You just 
			*** put the authentication code above inside the config file *** -->
	<authentication configSource="Configuration\Security\Authentication.config" />
</system.web>

You will notice the setting timeout is set to 5. This setting indicates that authentication will expire after 5 minutes. Also, there is a setting for slidingExpiration and it is set to true (true is the default value, so I didn't need to specify it - I just wanted to make it obvious). According to MSDN, the slidingExpiration and timeout settings work in conjunction.

Sliding expiration resets the expiration time for a valid authentication cookie if a request is made and more than half of the timeout interval has elapsed. If the cookie expires, the user must re-authenticate. Setting the SlidingExpiration property to false can improve the security of an application by limiting the time for which an authentication cookie is valid, based on the configured timeout value.

Let's use our example to describe this. If the user has been authenticated, a FormsAuthenticationTicket cookie has been created. This cookie has an Expiration property which is set to

DateTime.Now + Authentication.Timeout // 5 minutes in our example

If the user is inactive for 3 minutes and then becomes active, the Expiration value is once again evaluated - DateTime.Now + 5 minutes. This is seamless. However, if the user is inactive for 5 minutes and 30 seconds, the FormsAuthenticationTicket is expired and the user is forced to log on again.

Simple, right? But how does that stop simultaneous users on at a different device while the first session is inactive? It doesn't. But that is where the Membership provider comes in.

The Membership provider also appears within the system.web section of the web.config file. Below is an example.

<system.web>
	<authentication mode="Forms">
		<forms loginUrl="~/login.aspx" timeout="5" />
	</authentication>
	<membership defaultProvider="MembershipProvider" userIsOnlineTimeWindow="2">
		<providers>
		<clear />
			<add name="MembershipProvider"
			type="System.Web.Security.SqlMembershipProvider"
			connectionStringName="MembershipDb"
			applicationName="/MyApp"
			enablePasswordRetrieval="false"
			enablePasswordReset="true"
			requiresQuestionAndAnswer="false"
			requiresUniqueEmail="true"
			passwordFormat="Hashed"
			minRequiredNonalphanumericCharacters="0" 
			passwordStrengthRegularExpression="" />
		</providers>
	</membership>
</system.web>

I won't go into detail of the Membership section, but I need to direct your attention to the userIsOnlineTimeWindow property. Once again, MSDN says...

Specifies the number of minutes after the last-activity date/time stamp for a user during which the user is considered online.

This setting keeps a track of users and flags them as "online" according to this setting. I mentioned above that ASP.Net tracks FormsAuthentication in a cookie, so why try to record logon date/times and check each time the user requests something from the site. ASP.Net is already doing that. If you try to replicate that, you are putting more load on your server by duplicating the request code.

The values I have used are important too. I used a value of 5 minutes in the slidingExpiration setting and a value of 2 minutes in the userIsOnlineTimeWindow setting. This means that there will never be a delay between when the user authentication lapses and when ASP.Net considers that user to be still logged in. If the userIsOnlineTimeWindow value was set to 5 minutes too, the user may not be able to log on after authentication lapses because the system thinks they are still logged in, even though the authentication ticket has expired.

In most web applications, the system shows some user details in the header once the user is authenticated. To make sure the Membership provider keeps the user "online" while they are in the system, I put some code in the MVC Layout page (or Masterpage if you are in Webforms) which updates the Membership object and displays some meaningful user data. When the Membership method of GetUser is called with the boolean argument, the Membership provider resets the activity date for the user, therefore replicating the slidingExpiration setting in the Authentication provider. NB I added some debugging code in this sample too, so you can step through the authenticationTicket object and see how the cookie expiration date changes. I took that code from here.

//	get the user from the Membership provider. Ideally, this should be a in a try/catch to ensure the user is authenticated
MembershipUser u = Membership.GetUser(true);
//	debug data - you can step into the next two lines to see the authentication ticket data
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);

string userProfileData = String.Format("Last Login: {0}, UserName: {1}", u.LastLoginDate.ToLocalTime().ToShortDateString(), u.UserName);

And there you have it. A mechanism that tracks when users log on, whether they are still logged on and stops simultaneous logins using the same credentials. Feel free to add your comments.

Til next time ...