locked
How to prevent multiple & concurrent login in .Net Core 2.x ? RRS feed

  • Question

  • User-1013792694 posted

    I'm trying to limit user login in a web application. Some sources [1], [2] & [3] use same idea by using 

    await _userManager.UpdateSecurityStampAsync (loginUser); 

    This is the complete code of Login action in AccountController:

            [HttpPost]
            [AllowAnonymous]
            [ValidateAntiForgeryToken]
            public async Task<IActionResult> Login (LoginViewModel model, string returnUrl = null) {
                ViewData["ReturnUrl"] = returnUrl;
                if ( ModelState.IsValid ) {
    
                    var loginUser = await _userManager.FindByEmailAsync(model.Email);
                    if ( loginUser == null ) {
                        ModelState.AddModelError (string.Empty, "Invalid username/password.");
                        return View ();
                    }
    
                    await _userManager.UpdateSecurityStampAsync (loginUser);
                    var result = await _signInManager.PasswordSignInAsync(loginUser, model.Password, isPersistent: false, lockoutOnFailure: true);
    
                    // This doesn't count login failures towards account lockout
                    // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                    //var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
                    if ( result.Succeeded ) {
                        _logger.LogInformation ("User logged in.");
                        return RedirectToLocal (returnUrl);
                    }
                    if ( result.RequiresTwoFactor ) {
                        return RedirectToAction (nameof (LoginWith2fa), new { returnUrl, model.RememberMe });
                    }
                    if ( result.IsLockedOut ) {
                        _logger.LogWarning ("User account locked out.");
                        return RedirectToAction (nameof (Lockout));
                    } else {
                        ModelState.AddModelError (string.Empty, "Invalid login attempt.");
                        return View (model);
                    }
                }
    
                // If we got this far, something failed, redisplay form
                return View (model);
            }

    Above login action looks so familiar. It's just modified by adding UpdateSecurityStampAsync for updating the 'concurrencyStamp' and 'SecurityStamp' columns in the table (dbo.AspNetUsers) everytime user login before PasswordSignInAsync.

    I tried to screen shoot from 2 different browser (1st & 2nd attempt from chrome; 3rd & 4th attempt from firefox) with same login. 2nd & 4th was captured after execute UpdateSecurityStampAsync. 2nd & 3rd had same ConcurrencyStamp and SecurityStamp

    I also modified start.cs in ConfigureServices method:

                services.Configure<SecurityStampValidatorOptions> (option =>
                    option.ValidationInterval = TimeSpan.FromSeconds (0)
                );
    
                services.AddAuthentication ()
                    .Services.ConfigureApplicationCookie (options => {
                        options.SlidingExpiration = true;
                        options.ExpireTimeSpan = TimeSpan.FromMinutes (30);
                });

    But, after I modified those code above, I still can login, concurrently, from other web browser. I also can login from other machine successfully by using same user name & password.

    1. well, do I miss something ?
    2. Or, UpdateSecurityStampAsync cant be used to prevent users login concurrently ?
    3. Or, .Net Core can't handle this scenario and need 3rd party for user user login management ?

    Any help help/suggestion is highly appreciate.

    Tuesday, June 18, 2019 9:20 AM

All replies

  • User475983607 posted

    But, after I modified those code above, I still can login, concurrently, from other web browser. I also can login from other machine successfully by using same user name & password.

    1. well, do I miss something ?
    2. Or, UpdateSecurityStampAsync cant be used to prevent users login concurrently ?

    The UpdateSecurityStamp invalidates previous logins not the current login.  In other words, the most current login wins.  

    Or, .Net Core can't handle this scenario and need 3rd party for user user login management ?

    Identity works exactly as written if you have other requirements then it is up to you to design and build this logic.

    If you want one login at a time then simply add a last login DateTime field and update this field each time the user makes a request.  If the last login DateTime is X minutes old then another login, from the same user, is allowed.

    Tuesday, June 18, 2019 11:08 AM
  • User-474980206 posted

    the Security stamp is not for multiple logins, but used to detect if the login was modified and invalid the cookie cache. With security stamp validation, if you remove a users permission, or disable an account it will be detected on the next request. Without the stamp, it will be detected on cookie expiration.

    You will need a different timestamp (initial login of this cookie) to do what you want. 

    Tuesday, June 18, 2019 3:35 PM
  • User585649674 posted

    You need to a add a new column in User table, as Is_logged. set the value to 1 when you are singing in and also save the login timestamp. When the user logs off you need to set is_logged to 0. in each login you can check if its 1 or not. A background job to set all the is_logged to 0 if timestamp is greater than session time.

    you should also updated the login timestamp when the user has refreshed the token

    Wednesday, June 19, 2019 6:03 AM
  • User-1013792694 posted

    It's interesting idea. The next questing, how to create background job that regularly check time stamp ? 

    Or do you have any link or example for your interesting idea ? TIA

    You need to a add a new column in User table, as Is_logged. set the value to 1 when you are singing in and also save the login timestamp. When the user logs off you need to set is_logged to 0. in each login you can check if its 1 or not. A background job to set all the is_logged to 0 if timestamp is greater than session time.

    you should also updated the login timestamp when the user has refreshed the token

    Thursday, June 20, 2019 2:00 AM