Introduction
There are several sites there that prevent multiple logins, It only allows a single device for the same id so to do the same I have created a simple solution to solve this situation in C#. For example, we have a site that provides some kind of service based on subscription, if we allow multiple logins for multiple devices, in that case, we have lost because a single user shares his/her id with others so all can use the same service without payment. To prevent that case below solution works.
Solution
Here I implement one helper class, ActionFilterAttribute, and LoginHistory table to maintain the logins history of users.
LoginHistory.cs (SQL Table)
public class LoginHistory { [Key] public int ID { get; set; } public string UserId { get; set; } public string SessionId { get; set; } public bool LoggedIn { get; set; } }
HelperExtention.cs
public class HelperExtention { public static bool IsUserLoginStillTrue(string userId, string sessionId) { ApplicationDbContext context = new ApplicationDbContext(); IEnumerable<LoginHistory> logins = (from i in context.LoginHistory where i.LoggedIn == true && i.UserId == userId && i.SessionId == sessionId select i).AsEnumerable(); return logins.Any(); } public static bool IsUserLoggedOnElsewhere(string userId, string sessionId) { ApplicationDbContext context = new ApplicationDbContext(); IEnumerable<LoginHistory> logins = (from i in context.LoginHistory where i.LoggedIn == true && i.UserId == userId && i.SessionId != sessionId select i).AsEnumerable(); return logins.Any(); } public static void LogOutRestUsersForSameUserId(string userId, string sessionId) { ApplicationDbContext context = new ApplicationDbContext(); var logins = (from i in context.LoginHistory where i.LoggedIn == true && i.UserId == userId && i.SessionId != sessionId select i).ToList(); foreach (LoginHistory item in logins) { item.LoggedIn = false; } context.SaveChanges(); } }
GlobalActionFilterAttribute.cs
This method will be called the byASP.NET MVC framework before any action method executes.
public class GlobalActionFilterAttribute : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { } public override void OnActionExecuting(ActionExecutingContext filterContext) { var userID = HttpContext.Current.User.Identity.GetUserId(); if (filterContext.HttpContext.Session["sessionid"] != null) { if (HelperExtention.IsUserLoginStillTrue (userID, filterContext.HttpContext.Session["sessionid"].ToString())) { if (!HelperExtention.IsUserLoggedOnElsewhere (userID, filterContext.HttpContext.Session["sessionid"].ToString())) { } else { HelperExtention.LogOutRestUsersForSameUserId (userID, filterContext.HttpContext.Session["sessionid"].ToString()); } } else { filterContext.HttpContext.Session.Abandon(); AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); filterContext.Result = new RedirectResult("/Account/Login"); } } else { filterContext.HttpContext.Session.Abandon(); AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); filterContext.Result = new RedirectResult("/Account/Login"); } } private IAuthenticationManager AuthenticationManager { get { return HttpContext.Current.GetOwinContext().Authentication; } } }
Below is the controller code you can use as per requirements. I used an Identity server to authenticate but you can use a simple controller and logic also. The below method will just check the user is valid or not and if it’s valid it will insert login history in the LoginHistory table.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, change to shouldLockout: true var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); switch (result) { case SignInStatus.Success: { var sessionID = System.Web.HttpContext.Current.Session.SessionID; var userID = SignInManager.AuthenticationManager.AuthenticationResponseGrant.Identity.GetUserId(); Session["sessionid"] = sessionID; LoginHistory history = new LoginHistory { UserId = userID, SessionId = sessionID, LoggedIn = true }; if (!_db.LoginHistory.Any(x => x.SessionId.Equals(sessionID) && x.UserId.Equals(userID))) { _db.LoginHistory.Add(history); await _db.SaveChangesAsync(); } else { var oldHistory = _db.LoginHistory.FirstOrDefault(x => x.SessionId.Equals(sessionID) && x.UserId.Equals(userID)); if (oldHistory != null) { oldHistory.LoggedIn = true; _db.SaveChanges(); } } return RedirectToLocal(returnUrl); } case SignInStatus.LockedOut: return View("Lockout"); case SignInStatus.RequiresVerification: return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); case SignInStatus.Failure: default: ModelState.AddModelError("", "Invalid login attempt."); return View(model); } }
You can also write the below lines on registration if you want.
var sessionID = System.Web.HttpContext.Current.Session.SessionID; Session["sessionid"] = sessionID;
That’s it this works for me, you can use a piece of code or modify code as per your requirements.
hope you guys found something useful. Please give your valuable feedback/comments/questions about this article. Please let me know how you like and understand this article and how I could improve it. If you like this blog you can buy me Pizza.