In most serious ASP.NET MVC, or even legacy ASP.Net web sites, you are unlikely to want to use the default membership provider of ASP.Net. Its dependency on SQLServer and unhealthy predilection for littering databases with hundreds tables, just to support features you don’t care about, make it distinctly unattractive.
What we really want is to integrate our web site’s security with the project’s schema and bind directly to a table or repository encapsulating the users model for the site. The way to do this is through the implementation of a custom MembershipProvider.
This may seem a little daunting, but in practise is fairly simple. In fact, all we need do is override a pair of methods, on a couple of abstract classes, and all authentication and role checking will be routed to our code. Even better, by leveraging the well-tested and robust ASP.NET security facilities, we can still utilise the convenience and security of ASP.Net’s attribute based security to protect your controllers and controller methods. If you aren’t familiar with these, it’s as simple as attaching an Authorise tag, and optionally specifying a role the user must have to gain access e.g.
public class MemberController : Controller
{
IAccountRepository repo;
public MemberController(IAccountRepository accountRepository)
{
repo = accountRepository;
}
[Authorize]
public ActionResult Index()
{
...
}
[Authorize(Roles = "Admin")]
public ActionResult Delete(int accountId)
{
...
}
}
Just being authorised means the user has been authenticated i.e. logged in. The role is supplementary to this and allows you finer grained access control. Easy huh?
So, firstly authentication. To take control of this, we have to create a class derived from the abstract class MembershipProvider.
Unfortunately, being a hangover from old-school ASP.net, wiring this in is a little more clumsy than one might expect. It predates the pluggable design pattern applied throughout the MVC platform. The upshot being, that if you want to use the repository pattern with it, you can’t pass a repository into the constructor as ASP.Net instantiates the class for you, and only knows how to do this through a default constructor.
One way to work around this is to make your repository a property on the class and update it after the framework has constructed it. In this example I’m going to use ninject as my DI framework, but you could use a different one, or in fact just set the property without using DI at all.
First lets start with an example membership provider. I’ve only bothered to override ValidateUser() as the other methods aren’t required to leverage ASP.NET’s integrated security features.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using Ninject;
namespace MyProj.Web.Infrastructure
{
public class AccountMembershipProvider : MembershipProvider
{
[Inject]
public IAccountRepository AccountRepository { get; set; }
public override string ApplicationName
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
... lots of unimplemented overrides...
public override void UpdateUser(MembershipUser user)
{
throw new NotImplementedException();
}
public override bool ValidateUser(string username, string password)
{
return AccountRepository.IsValidLogin(username, password);
}
}
}
You’ll notice there is an AccountRepository being used here to validate the user login. This is where your custom authorisation logic goes – as such I’m not going to provide an implementation here as it will depend on the specifics of your site.
Next, we need to ensure that the AccountRepository is injected into our MembershipProvider, and the place to do this is Application_Start() in Gloabl.asax.
internal class MyNinjectModules : NinjectModule
{
public override void Load()
{
Bind<IAccountRepository>()
.To<AccountRepository>();
}
}
public class MvcApplication : System.Web.HttpApplication
{
private IKernel _kernel = new StandardKernel(new MyNinjectModules());
...code deleted....
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Inject account repository into our custom membership provider.
_kernel.Inject(Membership.Provider);
}
}
You’ll notice that I’m grabbing the framework instantiated instance of our membership provider, and using ninject to set the repository property on our custom membership provider.
I’m also setting up a ninject controller factory which is a great class when you want to inject repositories into your controller’s constructors. This is based on code found in one of the best programming books I’ve ever read Pro ASP.NET MVC 2 Framework by Steven Sanderson. This book covers so much more than ASP.NET MVC, it teaches you how to design and build applications for testability, and is the most digestable explanation of modern test driven design I’ve ever come across. It’s worth a read even if you aren’t an MVC programmer! n.b. I believe a revised MVC3 edition is to be released shortly.
public class NinjectControllerFactory : DefaultControllerFactory
{
private IKernel _kernel;
public NinjectControllerFactory(IKernel kernel)
{
_kernel = kernel;
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
return null;
return (IController)_kernel.Get(controllerType);
}
}
Now… what if we want to create a custom role provider too? Well that’s easy. Here is an exampe RoleProvider:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using Ninject;
namespace MyProj.Web.Infrastructure
{
public class AccountRoleProvider : RoleProvider
{
[Inject]
public IAccountRepository AccountRepository { get; set; }
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
... lots of unimplemented overrides...
public override string[] GetRolesForUser(string id)
{
return AccountRepository.GetRoles(id);
}
... lots of unimplemented overrides...
public override bool RoleExists(string roleName)
{
throw new NotImplementedException();
}
}
}
Again, you really don’t need to override much of the RoleProvider abstract class, simply implementing GetRolesForUser() and ensuring it returns a string array of the given users roles will suffice.
To inject the membership provider juat change the previous code version of Application_Start() to:
public class MvcApplication : System.Web.HttpApplication
{
private IKernel _kernel = new StandardKernel(new MyNinjectModules());
...code deleted....
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Inject account repository into our custom membership & role providers.
_kernel.Inject(Membership.Provider);
_kernel.Inject(Roles.Provider);
}
}
Finally we need to register our providers in web.config:
<configuration>
...
<system.web>
...
<membership defaultProvider="AccountMembershipProvider">
<providers>
<clear/>
<add name="AccountMembershipProvider"
type="MyProj.Web.Infrastructure.AccountMembershipProvider" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="AccountRoleProvider">
<providers>
<clear/>
<add name="AccountRoleProvider"
type="MyProj.Web.Infrastructure.AccountRoleProvider" />
</providers>
</roleManager>
...
</system.web>
...
</configuration>
It really us as simple as that!
One other note, if you are implementing your own password storage, make sure you hash them! (and I recommend you look at bcrypt for that).