Concurrent sessions - C-Sharp

Concurrent sessions - C-Sharp

Need

Enforce session management and limit concurrent user sessions

Context

  • Usage of C# 7.0 for modern language features and enhancements
  • Usage of Microsoft.AspNetCore.Mvc for building web applications using the ASP.NET Core MVC framework
  • Usage of Microsoft.AspNetCore.Identity for managing user authentication and authorization in ASP.NET Core applications

Description

Non compliant code

        //Login Controller
public class LoginController : Controller
{
    private readonly UserManager _userManager;
    private readonly SignInManager _signInManager;

    public LoginController(UserManager userManager, SignInManager signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }

    [HttpPost]
    public async Task Login(LoginModel model)
    {
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);

        if (result.Succeeded)
        {
            return RedirectToAction("Index", "Home");
        }

        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        return View(model);
    }
}
        
        

The above code represents the login controller in a typical ASP.NET Core application. The Login method is responsible for authenticating the user and starting a new session.

The PasswordSignInAsync method from SignInManager is used to sign in the user based on their email and password. If the sign in is successful, the user is redirected to the home page. If the sign in fails, an error message is added to the ModelState and the login view is returned with the model.

The vulnerability here is that there are no checks in place to validate the number of active sessions a user has. This means that a user can log in multiple times from different locations, and the application does not notify the user of this. This can be a security risk if an attacker obtains a user's credentials, as they could log in and perform actions under the user's identity without the user being aware.

Steps

  • Implement session management to track the number of active sessions per user.
  • When a user logs in, check if they already have an active session and handle accordingly.
  • If a user attempts to login while already having an active session, notify them and provide an option to terminate the existing session.
  • Prevent multiple concurrent sessions by enforcing a single active session per user.
  • Consider implementing session timeout to automatically log out inactive users.
  • Ensure that session management is implemented securely, protecting session data and preventing session hijacking.

Compliant code

        public class LoginController : Controller
{
    private readonly UserManager _userManager;
    private readonly SignInManager _signInManager;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public LoginController(UserManager userManager, SignInManager signInManager, IHttpContextAccessor httpContextAccessor)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _httpContextAccessor = httpContextAccessor;
    }

    [HttpPost]
    public async Task Login(LoginModel model)
    {
        var user = await _userManager.FindByEmailAsync(model.Email);
        if (user != null)
        {
            if (await _signInManager.IsSignedInAsync(user))
            {
                ModelState.AddModelError(string.Empty, "You are already logged in from another location. Do you want to logout from there and login here?");
                return View(model);
            }
        }

        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);

        if (result.Succeeded)
        {
            _httpContextAccessor.HttpContext.Session.SetString("UserSession", user.Id);
            return RedirectToAction("Index", "Home");
        }

        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        return View(model);
    }
}
        
        

The updated code now includes session management to track the number of active sessions per user.

When a user attempts to log in, the system first checks if the user is already logged in by calling the IsSignedInAsync method of the SignInManager. If the user is already logged in, an error message is added to the ModelState and returned to the view, notifying the user that they are already logged in from another location.

If the user is not already logged in, the system proceeds to authenticate the user's credentials. If the authentication is successful, a new session is created for the user by setting a session variable "UserSession" with the user's ID.

This approach ensures that each user can only have one active session at a time, preventing multiple concurrent sessions. It also provides a notification to the user if they attempt to log in while already having an active session, giving them the option to terminate the existing session.

In addition to these changes, consider implementing a session timeout to automatically log out inactive users, further enhancing the security of your application.

References