0

I am looking at various methods to implement Authentication in my MVC3 app. I would like to use my own code to do the authentication – something similar to Is it possible to create a Logon System with ASP.NET MVC but not use the MembershipProvider? (I know that there are other methods.) I would like to know once I authenticate a user using one of these methods, how do I get the user information to the Controller constructor. (By user information I mean username or userID).

One of the options I considered is putting that info into the Session. This works, but it is difficult to test since I get the Session out of the Context which does not exist during the test.

I would appreciate any ideas for how to pass user info to the controller constructor.

Community
  • 1
  • 1
O.O.
  • 1,973
  • 6
  • 28
  • 40

2 Answers2

0

No. Do not use Session for authentication. It's less secure, and unstable (sessions can be destroyed at will by the server).

In MVC, you don't need to use membership at all, but.. and I will make a point of emphasizing the but... Doing authentication correctly is not a trivial task. It's very very very easy to get it wrong and not even realize it. Even if you know what you're doing. It's something that should be heavily analyzed, tested, verified, and re-analyzed.

I would suggest, if you don't want to extend this effort, you should probably just use the default providers (there are several you can choose from).

But in any event, if you are determined to do it yourself, all you need is some way to verify the user. MVC does not integrate with the membership provider like WebForms does. It uses it for convenience. If you look in the default AccountController that is generated for you if you create an Internet project, all it does is call Membership.VerifyUser().

The truly important thing is the Authentication cookie system, which MS provides in the form of the FormsAuthentication class. I would VERY strongly recommend using this for the cookie management, unless you REALLY REALLY REALLY know what you are doing.

Just look in the AccountController, and it should be very obvious how this works. FormsAuthentication is the part that integrates into the app and tells asp.net that the user has already been authenticated. It uses a secure, encrypted cookie, and it's well designed (it even allows you to store your own additional data in an encrypted format).

Forms Authentication is a set of classes that work together to provide a transparent authentication mechanism, and is integrated into MVC and Asp.net WebForms. They are basically an implementation of the IPrincipal and IIdentity system, which is integral to asp.net (if you type User.IsAuthenticated this uses the IPrincipal interface).

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • My real question here is how to pass the user info to the Controller once Authenticated? I think my authentication implementation options would be restricted once I figure this question out. – O.O. Oct 16 '12 at 16:05
  • @O.O. - the answer is that you don't. If you use FormsAuthentication, then the authentication is done for you. If you use the property on the Controller `User` as in `User.Identity.Name` you get the username the user logged in with, then you look up the other info you need in your database from that. – Erik Funkenbusch Oct 16 '12 at 16:08
  • @O.O. - er.. I mean that if you use `FormsAuthentication` then the authentication cookie management is done for you, which also includes validating the cookie on subsequent pages, and populating the `HttpContext.Current.User` global variable (which will then propagate to the Controller and View). – Erik Funkenbusch Oct 16 '12 at 16:16
  • As I mentioned in my original post, I want to avoid the `HttpContext` if possible. This is the same problem I am facing now with the `Session` _(also got from the `HttpContext`)_ - that it is not too easy to Mock while testing. This question is more regarding passing user info to the Controller than the type of Authentication. I would look at Authentication once I have settled this issue. – O.O. Oct 16 '12 at 16:38
  • @O.O. - Actually, you didn't mention HttpContext in your original message. HttpContext is integral to the use of MVC, and you can't avoid using it. MVC has specific support for testing of HttpContext, which is why they wrap it in a testable HttpContextBase class. See http://stackoverflow.com/questions/1228179/mocking-httpcontextbase-with-moq – Erik Funkenbusch Oct 16 '12 at 16:47
  • @O.O. - If you're determined to not use MVC in the manner it was intended to be used with testing, then you can always use a method such as this: http://www.hanselman.com/blog/IPrincipalUserModelBinderInASPNETMVCForEasierTesting.aspx – Erik Funkenbusch Oct 16 '12 at 16:53
  • Thank you **Mystere Man**. I am sorry I did not explicitly mention that I used the `HttpContext` in the original post. I mentioned that I was using the `Session`, but I forgot to mention that I was getting the `Session` out of the `HttpContext`. For now, since my project is small with a dozen controllers, I have decided to create the controllers myself as shown in post below. – O.O. Oct 16 '12 at 21:53
0

In my original post I was looking at passing User Info to the Controller constructor. I did not want the Controller to depend on HttpContext, because this would make it difficult to test.

While I thank Mystere Man for his solution, I hope the following alternate solution would help someone. I have a small project (about a dozen controllers) so it is not too bad.

I basically created my custom ControllerFactory inheriting from DefaultControllerFactory:

public class MyCustomControllerFactory : DefaultControllerFactory
    {
        public MyCustomControllerFactory ()
        {
        }


        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
            {
                return null;
            }
            else
            {
                //Example of User Info - Customer ID
                string customerIDStr =  requestContext.HttpContext.Session["CustomerID"].ToString();
                int customerID = Int32.Parse(customerIDStr);

                //Now we create each of the Controllers manually
                if (controllerType == typeof(MyFirstController))
                {
                    return new MyFirstController(customerID);
                }
                else if (controllerType == typeof(MySecondController))
                {
                    return new MySecondController(customerID);
                }
                //Add/Create Controllers similarly
                else //For all normal Controllers i.e. with no Arguments
                {
                    return base.GetControllerInstance(requestContext, controllerType);
                }
            }
        }
    }

I then set the ControllerFactory in the Global.asax.cs Application_Start() method.

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);

            ControllerBuilder.Current.SetControllerFactory(new MyCustomControllerFactory ());
        }

P.S. I looked into using DI Containers like Ninject, but I think they were too complicated for my current project. I would look at them in a few months when it really makes sense to use them.

O.O.
  • 1,973
  • 6
  • 28
  • 40
  • I'm confused. Your project is "too small" to use the built-in HttpContext wrapper for mocking? You write a bunch of code to deal with a situation the framework itself gives you a solution for? Why? HttpContext in MVC is a wrapper class that wraps the real HttpContext so it can be unit tested. That's its purpose, and one of the major features of MVC in regards to testing. This post shows you how to mock it. http://stackoverflow.com/questions/32640/mocking-asp-net-mvc-controller-context/1959077#1959077 – Erik Funkenbusch Oct 16 '12 at 22:19
  • Thank you **Mystere Man**. I think I will do this later when my project gets a bit bigger. I don't want to mess with the `HttpContext` right now. – O.O. Oct 17 '12 at 16:44