My scenario is probably the opposite of most, I want ALLOW to have multiple simultaneous logins, but only for different types of users.
- User - has its own area.
- Administrator - has its own area.
The problem arises because the administrator can also be a user (they have two accounts, this basically means that they can check how the system works from the PoV user) and want them to log in both at the same time.
Using forms authentication is not possible. So I had to “crack” it a bit, and I worry that maybe I missed something.
Plan:
- Two action filters for each type of user: UserAuthorise & AdminAuthorise
- You have two session cookies for each type of user.
- Decorate controllers for which the correct action filter is based on the fact that the user can access it.
The code may require some disposal.
I will make cookie names more unique.
Excluded things, such as Views / Routes, as they do not seem relevant.
Left salt / hash password from samples and get stuck with test values.
UserAuthorise:
public class UserAuthorize : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var authCookie = filterContext.RequestContext.HttpContext.Request.Cookies["User"]; if (authCookie == null || authCookie.Value == "") { filterContext.HttpContext.Response.Redirect("/login"); base.OnActionExecuting(filterContext); return; } FormsAuthenticationTicket authTicket; try { authTicket = FormsAuthentication.Decrypt(authCookie.Value); } catch { filterContext.HttpContext.Response.Redirect("/login"); base.OnActionExecuting(filterContext); return; } if (authTicket.Expired || authTicket.Expiration <= DateTime.Now) { filterContext.HttpContext.Response.Redirect("/login"); } base.OnActionExecuting(filterContext); } }
AdminAuthorise:
public class AdminAuthorise : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var authCookie = filterContext.RequestContext.HttpContext.Request.Cookies["Admin"]; if (authCookie == null || authCookie.Value == "") { filterContext.HttpContext.Response.Redirect("/admin/login"); base.OnActionExecuting(filterContext); return; } FormsAuthenticationTicket authTicket; try { authTicket = FormsAuthentication.Decrypt(authCookie.Value); } catch { filterContext.HttpContext.Response.Redirect("/admin/login"); base.OnActionExecuting(filterContext); return; } if (authTicket.Expired || authTicket.Expiration <= DateTime.Now) { filterContext.HttpContext.Response.Redirect("/admin/login"); } base.OnActionExecuting(filterContext); } }
User Login Controller Action:
[HttpPost] public virtual ActionResult Login(FormCollection form) { if (form["username"] == "admin" && form["password"] == "pass") { var authTicket = new FormsAuthenticationTicket( 1, // version form["username"], // user name DateTime.Now, // created DateTime.Now.AddMinutes(20), // expires false, // persistent? "" // can be used to store roles ); string encryptedTicket = FormsAuthentication.Encrypt(authTicket); var authCookie = new HttpCookie("User", encryptedTicket); Response.Cookies.Add(authCookie); // Redirect back to the page you were trying to access return RedirectToAction(MVC.Home.Index()); } else { ModelState.AddModelError("", "Bad info mate"); } return View(); }
Admin Login Controller Action:
[HttpPost] public virtual ActionResult Login(FormCollection form) { if (form["username"] == "admin" && form["password"] == "pass") { var authTicket = new FormsAuthenticationTicket( 1, // version form["username"], // user name DateTime.Now, // created DateTime.Now.AddMinutes(20), // expires false, // persistent? "" // can be used to store roles ); string encryptedTicket = FormsAuthentication.Encrypt(authTicket); var authCookie = new HttpCookie("Admin", encryptedTicket); Response.Cookies.Add(authCookie); // Redirect back to the page you were trying to access return RedirectToAction(MVC.Admin.Home.Index()); } else { ModelState.AddModelError("", "Bad info mate"); } return View(); }
Does all this seem reasonable and safe?
In the "FireFox Page Information" window in cookies, I see that each user type has its own cookie, and you cannot access the user type area without logging in.