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.
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).
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.