4

My scenario is probably the opposite of most, I want to ALLOW multiple simultaneous logins but only for different types of users.

  • User — Has their own area
  • Admin — Has their own area

The problem occurs as an Admin can be a User as well (they have two accounts, this is mainly so they can check how the system is working from a user PoV) and want to be logged into both at the same time.

With Forms authentication, this doesn't seem possible. So I've had to "hack" around it slightly and am worried I might have overlooked something.

Plan:

  • Two action filters for each type of user: UserAuthorise & AdminAuthorise
  • Have two session cookies for each type of user
  • Decorate controllers which the correct action filter based on what user can access it.

Code might need some tidying up.

I'll make the cookie names more unique as well.

Excluded stuff like Views/Routes as they don't seem relevant.

Left password salting/hashing out of samples and 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 this all seem sensible and secure?

Looking in FireFox's Page Info window at cookies I see each user type has its own cookie and you can't access a user type area without logging in.

3 Answers 3

3

First, you should probably be deriving from AuthorizeAttribute rather than ActionFilterAttribute. AutorizationFilters exectue before ActionFilters, and allow short-circuiting (that is, if an authorization filter fails, action filters will never execute). Also, ActionFilters are chained together, and might execute in any order.

Second, it's not a good idea to have the admin username and password hard coded into the attribute. Passwords should really be one-way hashed.

Sign up to request clarification or add additional context in comments.

1 Comment

Yes username/password hashing/salting and pulling from the database was removed from my samples to keep it to the bits I really needed reviewing
1

What you need for this scenario is called impersonation, basically all you have to do is set a fake authentication cookie with the impersonated user data (so the admin can see what the customer see).

Probably you would also want to keep track of this, so you can place on the user interface of the admin impersonating the user infos about the state of the application, and also give it a link to end the impersonation session (you would at this point restore the previous cookie), instead of letting him "log in twice".

You can check this out, as it may contain some useful infos for you (a bit old but always valid).

1 Comment

interesting and will note for adding to my setup but not related to my actual question and usage scenarios
0

Regarding your database model, I'd assign several roles (simple user, admin, supervisor, etc) to a user. This way, you would login just once using a default role (admin), and have an option to switch to another role (simple user PoV) and store permissions on session.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.