locked
Maintaining list of logged in users to prohibit multiple logins RRS feed

  • Question

  • User-1802931265 posted

    Just want some advice on the best way of achieving something..

    We have a requirement to stop multiple logins for the same user.  Because asp.net has a session timeout, we don't want to have to wait around until that times out before allowing a user to log in again, so we want to kill the original user session if that user logs in again.

    What I was thinking of doing was creating an application state object that is a name_value pairs of usersnames as the key and sessionid as the value.  When a user logs in, it will either update or insert a new entry for the logged in user.   On begin_request, if user is logged in, it can check that object to determine whether the session id stored against the user is the same as the current one and if not, log them out.

    Any thoughts ?

    Thursday, September 26, 2019 8:39 PM

Answers

  • User-1802931265 posted

    When you close the browser, although the content of the cache is not cleared, the content of the session has been cleared.

    I do not believe this is correct..  the session remains until it reaches the session timeout after the last request from the client.. then and only then is the server side session cleared.  But, there is no way of looking at other sessions..  perhaps unless they are stored in a SQL Server store.

    I have an approach that seems to work..  

    Firstly I create a table an SQL stored procedure

    IF object_id('web_active_users') is not null
        drop table dbo.web_active_users;
    
    CREATE TABLE dbo.web_active_users (
        username    Varchar(50) not null,
        session_id  Varchar(80) not null,
        heartbeat   Datetime not null,
        primary key (username, session_id)
    )
    
    CREATE NONCLUSTERED INDEX ix_heartbeat ON dbo.web_active_users (heartbeat ASC);
    go
    
    IF object_id('dbp_web_active_users') is not null
        drop procedure dbo.dbp_web_active_users;
    go
    CREATE PROCEDURE dbo.dbp_web_active_users
        @Action      Varchar(10),
        @User        Varchar(50) = '',
        @SessionID   Varchar(80) = '',
        @Timeout     Integer = 20,
        @Result      Varchar(100) = '' Out
    AS
    BEGIN
    	SET NOCOUNT ON
    
        SELECT @Action = Upper(IsNull(@Action,'')),
               @Result = 'OK'
        
        -- Check that the Action parameter is valid
        IF @Action NOT IN ('LOGIN','HEARTBEAT') BEGIN
            RaisError('Incorrect parameter passed to dbp_web_users_active',16,1)
            RETURN
        END 
    
        -- General clean out of users who have old sessions
        DELETE FROM dbo.web_active_users
         WHERE DateAdd(Minute,@Timeout,heartbeat) < GetDate()
    
        -- if login or heartbeat, check user and session id parms
        IF @Action IN ('LOGIN','HEARTBEAT') BEGIN
            IF IsNull(@User,'') = '' OR IsNull(@SessionID,'') = '' BEGIN
                RaisError('Incorrect data passed to dbp_web_users_active',16,1)
                RETURN
            END
        END
    
        -- Use has logged in, remove any other rows for this user and create a new one for them
        -- This is called when a user has been authenticated
        IF @Action = 'LOGIN' BEGIN
            -- Firstly, lets invalidate any other sessions this user may have by deleting their records
            DELETE FROM dbo.web_active_users
             WHERE username = @User
    
            -- Finally lets create the new session entry
            INSERT INTO dbo.web_active_users (username, session_id, heartbeat)
                 VALUES (@User, @SessionID, Getdate())
        END
    
        -- Hearbeats are called when we get a web postback.. so attempt to update the datetime column.
        -- If this fails, then the user has either timed out or they've logged in elsewhere
        IF @Action = 'HEARTBEAT' BEGIN
            UPDATE dbo.web_active_users
               SET heartbeat = GetDate()
             WHERE username = @User
               AND session_id = @SessionID
    
            IF @@RowCount = 0 
                SET @Result = 'NOSESSION'   -- will log that user out because ze's either past session length or user logged in elsewhere
        END
    
    END;
    go
    

    When a user is authenticated we call the routine with the 'LOGIN' action, then in in global.asax Application_AcquireRequestState we call it with the HEARTBEAT action..  if we get a 'NOSESSION' response, we redirect to the logout page with a message 'Logged in elsewhere'

    Seems to work a treat and will continue to work across a farm if necessary.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, September 30, 2019 8:40 AM

All replies

  • User665608656 posted

    Hi Adrian_Parker,

    We have a requirement to stop multiple logins for the same user.

    You just want to prevent the same user from logging in many times If I understand correctly, right?

    If so ,you can use cache to achieve this function.

    Cache is a global object whose scope of action is the whole application and all users.

    As long as the user information after each user login is stored in Cache, the key name of Cache is set as the login name of the user, and the expiration time of Cache is set as the timeout time of Session.

    When the user logs in each time, the value of Cache[username] is judged. If there is no value, it proves that the user is not logged in, otherwise it should be used. The user has logged in.

    Here is an example:

    string strCacheKey = TextBox1.Text;
    string user = Convert.ToString(Cache[strCacheKey]);
    if (user == string.Empty)
    {
    TimeSpan SessTimeOut = new TimeSpan(0, 0, System.Web.HttpContext.Current.Session.Timeout, 0, 0);
    Cache.Insert(strCacheKey, strCacheKey, null, DateTime.MaxValue, SessTimeOut, CacheItemPriority.NotRemovable, null);
    Session["User"] = strCacheKey;
    Label1.Text = Session["User"].ToString();
    }
    else
    {
     Label1.Text = "The user has logged in!";
    }

    Other options:

    1. Maintain a flag in database; upon every login/out update the flag. For instance, upon every authentication request you can reject the login request if the flag is already true.
    2. Alternatively, you can maintain a list of users in the Application object and use .Contains to see if it already exists.

    For more details, you could refer to this link: 

    https://stackoverflow.com/a/2599197

    Best Regards,

    YongQing.

    Friday, September 27, 2019 9:10 AM
  • User-1802931265 posted

    Hi Yongqing Yu, 

    You just want to prevent the same user from logging in many times If I understand correctly, right?

    Nearly, but I think your approach is to determine whether the user has logged in already and if their session hasn't time out yet, stop them logging in again, but given that the timeout period is 20 minutes, that could be a pain if you login in, close your browser then try to login in again..  you'd have to wait 20 mins to get anywhere.

    Friday, September 27, 2019 10:31 AM
  • User665608656 posted

    Hi Adrian_Parker,

    Nearly, but I think your approach is to determine whether the user has logged in already and if their session hasn't time out yet, stop them logging in again, but given that the timeout period is 20 minutes, that could be a pain if you login in, close your browser then try to login in again..  you'd have to wait 20 mins to get anywhere.

    When you close the browser, although the content of the cache is not cleared, the content of the session has been cleared.

    So when you log in to the page, you need to judge at the same time, if the cache is not null, and the session is not null, then remind the user not to log in.

    If the cache value is not null and the session value is null, the user is allowed to log in.

    Best Regards,

    YongQing.

    Monday, September 30, 2019 5:48 AM
  • User-1802931265 posted

    When you close the browser, although the content of the cache is not cleared, the content of the session has been cleared.

    I do not believe this is correct..  the session remains until it reaches the session timeout after the last request from the client.. then and only then is the server side session cleared.  But, there is no way of looking at other sessions..  perhaps unless they are stored in a SQL Server store.

    I have an approach that seems to work..  

    Firstly I create a table an SQL stored procedure

    IF object_id('web_active_users') is not null
        drop table dbo.web_active_users;
    
    CREATE TABLE dbo.web_active_users (
        username    Varchar(50) not null,
        session_id  Varchar(80) not null,
        heartbeat   Datetime not null,
        primary key (username, session_id)
    )
    
    CREATE NONCLUSTERED INDEX ix_heartbeat ON dbo.web_active_users (heartbeat ASC);
    go
    
    IF object_id('dbp_web_active_users') is not null
        drop procedure dbo.dbp_web_active_users;
    go
    CREATE PROCEDURE dbo.dbp_web_active_users
        @Action      Varchar(10),
        @User        Varchar(50) = '',
        @SessionID   Varchar(80) = '',
        @Timeout     Integer = 20,
        @Result      Varchar(100) = '' Out
    AS
    BEGIN
    	SET NOCOUNT ON
    
        SELECT @Action = Upper(IsNull(@Action,'')),
               @Result = 'OK'
        
        -- Check that the Action parameter is valid
        IF @Action NOT IN ('LOGIN','HEARTBEAT') BEGIN
            RaisError('Incorrect parameter passed to dbp_web_users_active',16,1)
            RETURN
        END 
    
        -- General clean out of users who have old sessions
        DELETE FROM dbo.web_active_users
         WHERE DateAdd(Minute,@Timeout,heartbeat) < GetDate()
    
        -- if login or heartbeat, check user and session id parms
        IF @Action IN ('LOGIN','HEARTBEAT') BEGIN
            IF IsNull(@User,'') = '' OR IsNull(@SessionID,'') = '' BEGIN
                RaisError('Incorrect data passed to dbp_web_users_active',16,1)
                RETURN
            END
        END
    
        -- Use has logged in, remove any other rows for this user and create a new one for them
        -- This is called when a user has been authenticated
        IF @Action = 'LOGIN' BEGIN
            -- Firstly, lets invalidate any other sessions this user may have by deleting their records
            DELETE FROM dbo.web_active_users
             WHERE username = @User
    
            -- Finally lets create the new session entry
            INSERT INTO dbo.web_active_users (username, session_id, heartbeat)
                 VALUES (@User, @SessionID, Getdate())
        END
    
        -- Hearbeats are called when we get a web postback.. so attempt to update the datetime column.
        -- If this fails, then the user has either timed out or they've logged in elsewhere
        IF @Action = 'HEARTBEAT' BEGIN
            UPDATE dbo.web_active_users
               SET heartbeat = GetDate()
             WHERE username = @User
               AND session_id = @SessionID
    
            IF @@RowCount = 0 
                SET @Result = 'NOSESSION'   -- will log that user out because ze's either past session length or user logged in elsewhere
        END
    
    END;
    go
    

    When a user is authenticated we call the routine with the 'LOGIN' action, then in in global.asax Application_AcquireRequestState we call it with the HEARTBEAT action..  if we get a 'NOSESSION' response, we redirect to the logout page with a message 'Logged in elsewhere'

    Seems to work a treat and will continue to work across a farm if necessary.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, September 30, 2019 8:40 AM