Phil Fresle's Developer Blog

Preventing a User From Having Multiple Concurrent Sessions

This article is largely based on information learned within the book "Professional ASP.NET 3.5 Security, Membership, and Role Management with C# and VB" by Bilal Haidar.

A common question asked regarding ASP.NET is how can you prevent a user logging on more than once at the same time. Unfortunately, the nature of ASP.NET means that you cannot tell if a user is logged in already. Sure you can log the fact a user has accessed your application, but there is no way to tell that they have abandoned their old session, perhaps by closing their browser, and that their new login is therefore valid.

However, if we look at the problem from another angle there is a way to prevent concurrent access by a user. The steps we take are as follows:

  1. When the user logs in we generate a unique guid token to represent their session. We store this token in the authentication cookie, and also in the membership database within the comment property for the user.

  2. Every time the user accesses a page where he requires authenticated access we will check the token stored in the cookie against that stored in the membership database, and as long as they are identical we will allow access. However, if the user has logged in from another computer the token in the membership database will have changed and we will deny access and log them off from their old session.

To implement this in a web site we need to make several changes.

  1. We will make code changes to the LoggedIn event from the login control (or to the custom code we have written once the user has logged in). I place this code in a static method within a custom static class that I then call from the event which makes it easier to reuse in other projects.

  2. We will create a class that implements IHttpModule that will execute code for the OnPostAuthenticate event for all our pages that are authenticated. This separate class also makes it easy to implement in other projects.

  3. We will register the HttpModule in our web.config file.

So, onto the code. First my SingleSessionPreparation class that contains the method CreateAndStoreSessionToken to be called from the LoggedIn event.

 

using System;
using System.Web;
using System.Web.Security;

/// <summary>
/// SingleSessionPreparation is used to help ensure
/// users may only have one session active
/// </summary>
internal static class SingleSessionPreparation
{
    /// <summary>
    /// Called during LoggedIn event. Need to pass username
    /// as login process not fully completed
    /// </summary>
    internal static void CreateAndStoreSessionToken(string userName)
    {
        // Will be using the response object several times
        HttpResponse pageResponse = HttpContext.Current.Response;

        // 'session' token
        Guid sessionToken = System.Guid.NewGuid();

        // Get authentication cookie and ticket
        HttpCookie authenticationCookie =
            pageResponse.Cookies[FormsAuthentication.FormsCookieName];
        FormsAuthenticationTicket authenticationTicket =
            FormsAuthentication.Decrypt(authenticationCookie.Value);

        // Create a new ticket based on the existing one that includes the 'session' token in the userData
        FormsAuthenticationTicket newAuthenticationTicket =
            new FormsAuthenticationTicket(
            authenticationTicket.Version,
            authenticationTicket.Name,
            authenticationTicket.IssueDate,
            authenticationTicket.Expiration,
            authenticationTicket.IsPersistent,
            sessionToken.ToString(),
            authenticationTicket.CookiePath);

        // Store session token in Membership comment
        // You may want to store other information in the comment
        // field, if so, you may have to implement some dilimited
        // structure within it, perhaps xml
        MembershipUser currentUser = Membership.GetUser(userName);
        currentUser.Comment = sessionToken.ToString();
        Membership.UpdateUser(currentUser);

        // Replace the authentication cookie
        pageResponse.Cookies.Remove(FormsAuthentication.FormsCookieName);

        HttpCookie newAuthenticationCookie = new HttpCookie(
            FormsAuthentication.FormsCookieName,
            FormsAuthentication.Encrypt(newAuthenticationTicket));

        newAuthenticationCookie.HttpOnly = authenticationCookie.HttpOnly;
        newAuthenticationCookie.Path = authenticationCookie.Path;
        newAuthenticationCookie.Secure = authenticationCookie.Secure;
        newAuthenticationCookie.Domain = authenticationCookie.Domain;
        newAuthenticationCookie.Expires = authenticationCookie.Expires;

        pageResponse.Cookies.Add(newAuthenticationCookie);
    }
}

 

Then my SingleSessionEnforcement class that will stop the user from logging in multiple times:

using System;
using System.Web;
using System.Web.Security;

/// <summary>
/// Enforces a single login session
/// Needs an entry in Web.Config, exactly where depends on the version of IIS, but you
/// can safely put it in both places.
/// 1:
///  <system.web>
///     <httpModules>
///      <add name="SingleSessionEnforcement" type="SingleSessionEnforcement" />
///    </httpModules>
///  </system.web>
/// 2:
///  <system.webServer>
///    <modules runAllManagedModulesForAllRequests="true">
///      <add name="SingleSessionEnforcement" type="SingleSessionEnforcement" />
///    </modules>
///  </system.webServer>
/// Also, slidingExpiration for the forms must be set to false, also set a
/// suitable timeout period (in minutes)
///  <authentication mode="Forms">
///   <forms protection="All" slidingExpiration="false" loginUrl="login.aspx" timeout="600" />
///  </authentication>
/// </summary>
public class SingleSessionEnforcement : IHttpModule
{
    public SingleSessionEnforcement()
    {
        // No construction needed
    }

    private void OnPostAuthenticate(Object sender, EventArgs e)
    {
        Guid sessionToken;

        HttpApplication httpApplication = (HttpApplication)sender;
        HttpContext httpContext = httpApplication.Context;

        // Check user's session token
        if (httpContext.User.Identity.IsAuthenticated)
        {
            FormsAuthenticationTicket authenticationTicket =
                ((FormsIdentity)httpContext.User.Identity).Ticket;

            if (authenticationTicket.UserData != "")
            {
                sessionToken = new Guid(authenticationTicket.UserData);
            }
            else
            {
                // No authentication ticket found so logout this user
                // Should never hit this code
                FormsAuthentication.SignOut();
                FormsAuthentication.RedirectToLoginPage();
                return;
            }

            MembershipUser currentUser = Membership.GetUser(authenticationTicket.Name);

            // May want to add a conditional here so we only check
            // if the user needs to be checked. For instance, your business
            // rules for the application may state that users in the Admin
            // role are allowed to have multiple sessions
            Guid storedToken = new Guid(currentUser.Comment);

            if (sessionToken != storedToken)
            {
                // Stored session does not match one in authentication
                // ticket so logout the user
                FormsAuthentication.SignOut();
                FormsAuthentication.RedirectToLoginPage();
            }
        }
    }

    public void Dispose()
    {
        // Nothing to dispose
    }

    public void Init(HttpApplication context)
    {
        context.PostAuthenticateRequest += new EventHandler(OnPostAuthenticate);
    }
}

 

Changes to web.config to ensure that SingleSessionEnforcement is called:

 

  <system.web>
    <httpModules>
      <add name="SingleSessionEnforcement" type="SingleSessionEnforcement" />
    </httpModules>
  </system.web>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="SingleSessionEnforcement" type="SingleSessionEnforcement" />
    </modules>
  </system.webServer>

 

And finally, code to our LoggedIn event:

 

protected void LoginUser_LoggedIn(object sender, EventArgs e)
{
    TextBox userNameTextBox = (TextBox)LoginUser.FindControl("UserName");

    SingleSessionPreparation.CreateAndStoreSessionToken(userNameTextBox.Text);
}

 



Feedback

# re: Preventing a User From Having Multiple Concurrent Sessions

Nice post helps me a lot!! Thanks for sharing this!! 5/17/2010 6:04 PM | Andre Luiz

# re: Preventing a User From Having Multiple Concurrent Sessions

Invalid value for 'encryptedTicket' parameter.
Authenticationcookie.value =null 6/2/2010 10:00 PM | Kaleem

# re: Preventing a User From Having Multiple Concurrent Sessions

Are you using Forms Authentication and calling the code in the LoggedIn event? 6/3/2010 6:50 AM | Phil

# re: Preventing a User From Having Multiple Concurrent Sessions

Thanks U Posting
But ..ERROR.

Invalid value for 'encryptedTicket' parameter.
Authenticationcookie.value =null 8/2/2010 9:08 AM | mobilepro

# re: Preventing a User From Having Multiple Concurrent Sessions

Worked beautifully, thank you kindly for sharing.
10/27/2010 5:05 PM | Morten

# re: Preventing a User From Having Multiple Concurrent Sessions

I'm getting....

CS0122: 'SingleSessionPreparation' is inaccessible due to its protection level

What would be left out to cause this? 1/8/2011 12:36 AM | Andrew

# re: Preventing a User From Having Multiple Concurrent Sessions

LoggedIn event is not firing 3/16/2011 7:01 AM | Mushtaq

# re: Preventing a User From Having Multiple Concurrent Sessions

Plz help me .

multiple login not alloaw oa a single machine.
plz help me
mohit.chauhan8@gmail.com
4/2/2011 7:31 AM | Mohit Chauhan

# re: Preventing a User From Having Multiple Concurrent Sessions

instead of userName pass the ip address,

FormsAuthentication.Authenticate(Request.ServerVariables("REMOTE_ADDR"))

singleSessionPreparation.CreateAndStoreSessionToken(Request.ServerVariables("REMOTE_ADDR"))

on class SingleSessionEnforcement
replace Membership.GetUser for your own method to retrive de guid.id

I hope this help. 4/27/2011 3:51 PM | AnaO

# re: Preventing a User From Having Multiple Concurrent Sessions

Thanks a lot!!, i used that in vbnet without membership and only one user for ip address.
Also implement a cache for httpContext to save total of open windows with diferent sesion for ip address, that cache is only for ips open more than one time. I do that to reduce the calling of database only when are more open windows or tabs in the machine. If the user open a tab and this tab have same session i block de user for enter the site, that control is on login page.

Private Sub OnPostAuthenticate(ByVal sender As Object, ByVal e As EventArgs)
Dim sessionToken As String
Dim httpApplication As HttpApplication = CType(sender, HttpApplication)
Dim httpContext As HttpContext = httpApplication.Context
Dim totalCachePorIp As Integer = 1 'total de caches por nombre de ip, indica cantidad de ventas de ie abiertas
If httpContext.Current.Cache.Get(httpContext.Current.Request.ServerVariables("REMOTE_ADDR")) Is Nothing Then
Exit Sub
Else
totalCachePorIp = httpContext.Current.Cache.Get(httpContext.Current.Request.ServerVariables("REMOTE_ADDR"))
End If
'// Check user's session token
If httpContext.User.Identity.IsAuthenticated Then
Dim authenticationTicket As FormsAuthenticationTicket = CType(httpContext.User.Identity, FormsIdentity).Ticket
If authenticationTicket.UserData <> "" Then
sessionToken = New String(authenticationTicket.UserData)
Else
' // No authentication ticket found so logout this user
' // Should never hit this code
FormsAuthentication.SignOut()
FormsAuthentication.RedirectToLoginPage()
Exit Sub
End If
Dim clSdata As New clsDataSource.FastConnection(httpContext.Server.MapPath(System.Configuration.ConfigurationManager.AppSettings("ConnectionOtorgamiento")))
Dim storedToken As String = clSdata.RetornaUnValor("select Sessionid from Db_Log_Ctrl_Sesion where Id_Maquina = '" & httpContext.Current.Request.ServerVariables("REMOTE_ADDR") & "' and Activa = 1")
'// May want to add a conditional here so we only check
'// if the user needs to be checked. For instance, your business
'// rules for the application may state that users in the Admin
'// role are allowed to have multiple sessions
If sessionToken <> storedToken Then
' // Stored session does not match one in authentication
' // ticket so logout the user
totalCachePorIp += 1
If totalCachePorIp = 1 Then
System.Web.HttpContext.Current.Cache.Remove(httpContext.Current.Request.ServerVariables("REMOTE_ADDR"))
Else
System.Web.HttpContext.Current.Cache.Insert(httpContext.Current.Request.ServerVariables("REMOTE_ADDR"), totalCachePorIp - 1, Nothing, DateTime.MaxValue, TimeSpan.FromMinutes(20))
End If
FormsAuthentication.SignOut()
FormsAuthentication.RedirectToLoginPage()
End If
End If
End Sub

I would like to post my code and write in spanish, of course if the author let me doit.

4/27/2011 8:42 PM | AnaO

# re: Preventing a User From Having Multiple Concurrent Sessions

Worked for me, thanks. I received the same error as Andrews and had to change my class from internal to public, internal static void to public static void. Not exactly sure of the ramifications. Great solution for the last user to login wins scenario, still looking for one to prevent the second attempt from gaining access. 6/16/2011 6:34 PM | LostArk

# re: Preventing a User From Having Multiple Concurrent Sessions

It Will Give Error:- encryptedTicket is NULL
3/5/2012 12:54 PM | sandip

# re: Preventing a User From Having Multiple Concurrent Sessions

Thanks for sharing this articale But i have one problem am not using form auth method to login
the system throw exception when i loing here is my loign button click pls help:

if (DBAccess.IsValidUser(txtUserName.Text, txtPassword.Text, chkStatus.Checked.ToString()))
{




SingleSessionPreparation.CreateAndStoreSessionToken(txtUserName.Text);


Session["UserName"] = txtUserName.Text.Trim();
//Session["userPass"] = txtPassword.Text;

Session["True"] = chkStatus.Checked.ToString();


if (chkStatus.Checked)
{
Response.Redirect("UserArea/UserArea.aspx");
}
else
Response.Redirect("client_login.aspx");
}
else
{
lblError.Visible = true;
lblError.Text = "Invalid UserName / Password";
lblError.BackColor = Color.Red;
lblError.ForeColor = Color.White;
}
3/18/2012 12:49 PM | Noor

# re: Preventing a User From Having Multiple Concurrent Sessions

Sir, here is my error details for the above code when i try to loing
encryptedTicket parameter 3/18/2012 12:51 PM | Noor

# re: Preventing a User From Having Multiple Concurrent Sessions

Invalid value for 'encryptedTicket' parameter.
How to solve this issue..? 8/16/2012 12:54 PM | LIN

# re: Preventing a User From Having Multiple Concurrent Sessions

Trying to call singlesessionenforcement at global.asax as below

void Application_OnPostAuthenticateRequest(Object sender, EventArgs e)
{
SingleSessionEnforcement ForceSingelSession = new SingleSessionEnforcement();
ForceSingelSession.OnPostAuthenticate(sender, e);
}


Is it the right way ? Please help. 4/29/2013 8:29 AM | siba

# re: Preventing a User From Having Multiple Concurrent Sessions

Invalid value for 'encryptedTicket' parameter.
How to solve this issue..? 9/3/2013 5:30 AM | Nikunj Balar

# re: Preventing a User From Having Multiple Concurrent Sessions

Invalid value for 'encryptedTicket' parameter.
How to solve this ??? please reply me fast 9/19/2013 12:03 PM | raju

# re: Preventing a User From Having Multiple Concurrent Sessions

To fix error: Invalid value for 'encryptedTicket' parameter.

Add:

System.Web.Security.FormsAuthentication.SetAuthCookie(UserName, True)


Before:

// Get authentication cookie and ticket
HttpCookie authenticationCookie =
pageResponse.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket authenticationTicket =
FormsAuthentication.Decrypt(authenticationCookie.Value);


3/21/2014 7:21 PM | El Bananero

# re: Preventing a User From Having Multiple Concurrent Sessions

Great job here! 3/26/2014 1:16 PM | David Jiboye

# re: Preventing a User From Having Multiple Concurrent Sessions

getting Invalid value for 'encryptedTicket' parameter error even adding
System.Web.Security.FormsAuthentication.SetAuthCookie(UserName, True) 4/1/2014 2:24 PM | s

# re: Preventing a User From Having Multiple Concurrent Sessions

Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: Could not load type 'SingleSessionEnforcement'. (C:\Documents and Settings\palak\my documents\visual studio 2010\Projects\PalakDemo\PalakDemo\web.config line 63)

Source Error:


Line 61:
Line 62: <httpModules>
Line 63: <add name="SingleSessionEnforcement" type="SingleSessionEnforcement" />
Line 64: </httpModules>
Line 65: </system.web>




this error occured so what can i do please help me. 4/4/2014 6:49 AM | Palak

# re: Preventing a User From Having Multiple Concurrent Sessions

Hello sir

I am getting this error "Could not load type 'Single Session Enforcement'." 5/28/2014 5:23 AM | sunil

# re: Preventing a User From Having Multiple Concurrent Sessions

How to remove sessionID when session expires. 8/5/2014 4:09 AM | Khang Vi