Click here to Skip to main content
Licence CPOL
First Posted 18 Oct 2008
Views 19,672
Downloads 380
Bookmarked 49 times

A better way to implement exclusive login, the account can only be used by one person at a time

By | 18 Oct 2008 | Article
This article brings a better approach to implement exclusive login in ASP.NET.

2.jpg

Introduction

In some situations, we need exclusive login, which means double login is disabled, the account can only be used by one person at a time. In other words, two users can not use the same account at the same time from different places.

I have looked at some popular solutions. Usually, people do it in this way: the session timeout is set to a small number, like 1 minute. For the logged in user, the session is kept alive by a background timer or something else. And the login state is stored in a global place. When the session is started, the login state is initiated, otherwise if the session ends, the login state is released.

There are some defects in this solution:

  1. HTTP connection is not a long-time connection, so if the user closes the page, Session_End is not triggered immediately. That causes many problems.
  2. If I login using Internet Explorer and then I want to switch to Firefox, and if I forget to click on the "sign out" link on the page before I close IE, then the site won't allow me to login in Firefox before the session times out.

  3. If we keep the session alive by a background process, for example, we use the setTimeout in JavaScript, and refresh a back-end page every 30 seconds, that will increase the pressure on the server.

We need a better solution, and here I have one:

  1. We always allow account login.
  2. We store the latest login session ID for the given account in a global place.
  3. We monitor every request, and detect that if the current user is signed in but the session ID is not equal to the latest session ID of this account, kick the user out.

Background

To implement it in this way, we need extra code in two places:

  1. login process
  2. PreInit in the HTTPModule

Next, I will explain the code.

Using the code

First, we need some variables.

/// <summary>
/// This Hashtable stores the UserID--SessionID Pair
/// The UserID is the key
/// The SessionID is the latest session id for the login user.
/// </summary>
private static Hashtable s_UserID_SessionID_Pair_Collection = new Hashtable();

/// <summary>
/// getter/setter for the userid of current user
/// if the current user does not login in, getter return -1L
/// </summary>
public static long CurrentUserID
{
    get
    {
        if (HttpContext.Current.Session == null ||
            HttpContext.Current.Session["CurrentUserID"] == null)
        {
            return -1L;
        }
        return (long)HttpContext.Current.Session["CurrentUserID"];
    }
    set
    {
        HttpContext.Current.Session["CurrentUserID"] = value;
    }
}

Next, in the login process...

protected void btnLogin_Click(object sender, EventArgs e)
{
    //////////////////////////////////////////////////////////////////////////
    // login process start

    // first, check the username and password; here, always correct
    // ... some code to check the username/password ...

    // second, get the user id by username
    // from database; here, give a const value
    // ... some code to get the user id by username ...
    const long userId = 288L;

    // third, save state
    LoginState.Login(userId);

    // finnaly, redirect; here, just keep on the same page
    Response.Redirect("Default.aspx");
    
    // login process end
    //////////////////////////////////////////////////////////////////////////            
}

The LoginState.Login() is listed here:

/// <summary>
/// The user Sign In
/// </summary>
/// <param name="userid">User ID</param>
public static void Login(long userid)
{
    CurrentUserID = userid;
    s_UserID_SessionID_Pair_Collection[userid] = 
           HttpContext.Current.Session.SessionID;
}

Now add an HTTPModule class:

public class DoubleLoginMonitorModule : IHttpModule
{
    public String ModuleName
    {
        get { return "DoubleLoginMonitorModule"; }
    }

    public void Init(HttpApplication application)
    {
        application.PreRequestHandlerExecute += 
           new EventHandler(Application_PreRequestHandlerExecute);
    }

    private void Application_PreRequestHandlerExecute(Object source, EventArgs e)
    {
        HttpApplication application = (HttpApplication)source;
        HttpContext context = application.Context;

        // if double login, kick it out
        if (LoginState.IsDoubleLogin())
        {
        HttpContext.Current.Response.Redirect("logoff.htm");
        HttpContext.Current.Response.End();
        }
    }

    public void Dispose()
    {
    }
}

The LoginState.IsDoubleLogin() is listed in detail here:

/// <summary>
/// check whether the current user is double login
/// </summary>
/// <returns>true:Yes, the user is double login; false:No</returns>
public static bool IsDoubleLogin()
{
    if (CurrentUserID == -1L)
    return false;

    bool isDoubleLogin =
       string.Compare( (string)
       s_UserID_SessionID_Pair_Collection[CurrentUserID]
    , HttpContext.Current.Session.SessionID
    ) != 0;
    if (isDoubleLogin)
    {
    HttpContext.Current.Session.Clear();
    HttpContext.Current.Session.Abandon();
    }
    return isDoubleLogin;
}

Finally, modify the web.config, and add the HTTP Module:

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
    
    <!-- Add the HttpModule for IIS6 -->
    <httpModules>
      <add name="DoubleLoginMonitorModule" 
        type="DisableDoubleLogin.DoubleLoginMonitorModule" />
    </httpModules>
    </system.web>

  <!-- Add the HttpModule for IIS7 -->
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules>
      <add name="DoubleLoginMonitorModule" 
        type="DisableDoubleLogin.DoubleLoginMonitorModule" />
    </modules>
  </system.webServer>
</configuration>

Afterword

It is very simple, isn't it?

For the servers behind load balancing, we need to care about:

  1. Keep the session sync, MS provides several options like the ASP.NET State Service.
  2. The SessionID--AccountID pair should be stored in a global place, database is a good choice.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jerry.Wang

Architect
Best Brain
China China

Member

Jerry is working for the Best Brain ChangSha Office, China. He has been being interested in computer programing from his childhood. Skilled in Windows/Linux C++, Mac OS Objective-C, .Net & C#, ASP.Net, AS3/JS/PHP etc.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralGood article PinmemberDonsw13:17 28 Jan '09  
GeneralRe: Good article Pinmemberwangyanpeng16:07 12 Feb '09  
GeneralHi, Jerry.Wang PinmemberDuyThuong16:42 22 Oct '08  
I want do program the the same but us ASP not ASP.NET. You can help me, Please? And demo it on language ASP.
Thanks You so much
 
nhatphong

GeneralRe: Hi, Jerry.Wang PinmemberJerry.Wang16:51 22 Oct '08  
GeneralRe: Hi, Jerry.Wang [modified] PinmemberDuyThuong22:04 23 Oct '08  
GeneralRe: Hi, Jerry.Wang Pinmemberyeskele17:06 13 Nov '08  
GeneralRe: Hi, Jerry.Wang PinmemberJerry.Wang14:37 16 Nov '08  
GeneralRe: Hi, Jerry.Wang Pinmemberxudb18:58 21 Jan '10  
GeneralRe: Hi, Jerry.Wang PinmemberJerry.Wang5:40 10 Feb '10  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120529.1 | Last Updated 19 Oct 2008
Article Copyright 2008 by Jerry.Wang
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid