Using ASP.Net MVC without a MembershipProvider

Most people when they get to securing their ASP.Net MVC applications and managing users look to the MembershipProvider and the RoleProvider – or the new SimpleMembership. For many projects you then quickly realise that they don’t quite do what you want and you end up implementing your own versions of the MembershipProvider and the RoleProvider. I have certainly done that myself.

A big part of the reason is that, surely, lots of stuff in ASP.Net MVC is tied in to those standard providers and if you want to use nice things like the Authorize attribute then surely you need to use the Providers? As it turns out, this isn’t the case at all. There is a lot less black magic involved and I will claim that there is less work involved if you completely drop the providers compared to implementing your own versions of them. A lot of that has to do with the mental overhead involved in trying to figure out what is going on and figuring out which methods you need to implement.

I strongly recommend reading through Brock Allen’s excellent blog posts on ASP.Net MVC security in which he also explains why the MembershipProvider and the RoleProvider are poor, leaky and heavyweight abstractions.

Firstly, let’s just be clear: You don’t want to write your own logic for storing passwords and you don’t want to write your own logic for handling authentication cookies. ASP.Net MVC has got you covered – those bits are catered for by the framework without you having to use the providers at all.

In this post I will demonstrate what actually happens when you use the providers in order to build the understanding and then I will show you a simple example of an app that doesn't use them at all. For this example I am using forms authentication throughout – you know, the one where you type your username and password into a box on a screen and you get a little authentication cookie.

In summary, the MembershipProvider is intended as an abstraction for you to manage users and validate their passwords. It is not actually involved in the request processing at all – the Authorize atrribute will not call the MembershipProvider. In the Web Forms days, the MembershipProvider was quite handy because there were built-in controls that knew how to use the providers. But in MVC you have to write all that yourself anyway so the MembershipProvider really doesn’t give you a lot.

The RoleProvider on the other hand is used in each request: It is asked to provide the list of roles for a user. Luckily, it is very easy to replace that functionality with your own code as we shall see.

FormsAuthentication is the actual important workhorse. That is the one that is responsible for setting the authentication cookie securely, for reading and checking it on each incoming request, for setting HttpContext.Current.User, for managing sliding expiration and much more.

If you want to follow along with a full code sample, it is all on GitHub at https://github.com/flytzen/NoMembershipProviderSample.

git clone https://github.com/flytzen/NoMembershipProviderSample


Using FormsAuthentication to log in with no MembershipProvider and no RoleProvider

In this first step, we will show how you can use Forms Authentication to log a user in and use the Authorize attribute without any providers at all.

git checkout step-1 if you want to follow along.

All you need to enable you to login is this in web.config:
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Home/Login" />
</authentication>
....
</system.web>
This will enable FormsAuthentication and will make ASP.Net look out for a forms authentication cookie on all requests and populate HttpContext.Current.Principal with whatever the cookie says.

You need some login logic as well. In our simplified example we don’t use a password so a controller action like this will set the authentication cookie:
[HttpPost]
public ActionResult Login(string userName)
{
FormsAuthentication.SetAuthCookie("flytzen", false);
return RedirectToAction("Index");
}

Of course, you want to do something much smarter than that, such as validating the user – we will get to that. As a side note, you will also be given a returnUrl parameter that you should use to return the user to the URL they were trying to access.

Without anything else, you can now use the [Authorize] attribute and the [Authorize(Users = "username")] attribute. For some scenarios, this may be all you need.

However, if you try to use [Authorize(Roles = "Administrator")] you will always be redirected to the login screen as you have not done anything to tell ASP.Net what roles this particular user has. For the shortcut on how to handle that, see http://brockallen.com/2012/05/23/think-twice-about-using-roleprovider/

How much does ASP.Net really use MembershipProvider and RoleProvider?

Before we carry on with building our example, let's take a little detour and explore how tightly coupled - or not - ASP.Net MVC is to the providers.

One of the challenging things about handling users in ASP.Net MVC is that it is not at all clear how tightly coupled the MembershipProvider and the RoleProvider are into the framework. This means you don’t really know which methods you need to implemented – and it makes it hard to reason about whether to go down that pattern or just roll your own. We will test this out by implementing our own providers and see what is actually called.

For the purposes of finding this out, we’ll create a MyMembershipProvider and a MyRoleProvider which inherits from the built-in MembershipProvider and RoleProvider and throws NotImplementedException on every single method.

git checkout step-2 if you are following along.

We need to tell ASP.Net about by inserting the following in Web.config under <system.web>:
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear />
<add name="MyMembershipProvider" type="NoMembershipProviderSample.Providers.MyMembershipProvider" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear />
<add name="MyRoleProvider" type="NoMembershipProviderSample.Providers.MyRoleProvider" />
</providers>
</roleManager>

As long as you are not logged in, your app continues to work. However, as soon as you have logged in, you will see that ASP.Net tries to call MyRoleProvider.GetRolesForUser. This happens on every request when you are logged in, irrespective of which URL you access – not only when you hit an action that has [Authorize(Roles=”xxx”)].

The only thing you have to do to make everything work again is to change MyRoleProvider like this:
public override string[] GetRolesForUser(string username)
{
return new[] {"Administrator"};
}

git checkout step-3

To look at this another way, apart from one single method on RoleProvider, everything that the MembershipProvider and the RoleProvider give you is a means to manage your users and roles and to verify a password on your login screen. This made a lot of sense in the days of Web Forms when you had standard controls that could talk to the Providers. But in MVC you are writing all that yourself anyway. So, I believe it is actually less work to just write the code you need and not bother with the whole provider thing. So that’s what we’ll do next.

Implementing it yourself without using the providers

In this first step we will enable you to create users and validate their passwords.

git checkout step-4

Note that all of this code is rough sample code. For starters it should have error handling and it should certainly all be implemented with dependency injection etc. I am just trying to give you an idea of how simple this fundamentally is here, so forgive the messy code.

Storing passwords is dangerous territory and unless you have a degree in cryptography is probably not something you want to do yourself. The good news is that MVC provides a helper method that will generate and validate properly salted password hashes for you. It’s called System.Web.Helpers.Crypto. For a super simple example, I have written the logic into an extension method as seen below. In the real world, you probably want to write a UserManager class and wrap Crypto in an interface so you can dependency inject it and have proper unit tests. Remember, this is just sample code.
public static class AccountExtensions
{
public static void SetPassword(this Account account, string password)
{
account.PasswordHash = Crypto.HashPassword(password);
}

public static bool ValidatePassword(this Account account, string password)
{
return Crypto.VerifyHashedPassword(account.PasswordHash, password);
}
}

And that is really all there is to it. When I create a new user I just use the SetPassword method to generate a properly salted password.

In the login method in the controller you can do something like this:
[HttpPost]
public ActionResult Login(string userName, string password, string returnUrl)
{
var repo = new AccountRepository();
var user = repo.FindByName(userName);
if (user != null && user.ValidatePassword(password))
{
FormsAuthentication.SetAuthCookie(userName, false);
if (returnUrl != null && Url.IsLocalUrl(returnUrl))
return Redirect(returnUrl);
else
return RedirectToAction("Index");
}
ModelState.AddModelError("", "Invalid user name or password");
return View();
}

Again, it’s ugly sample code. You would of course want to DI the repository etc. In fact, you would probably want to have a User Manager that could retrieve the user and validate the password etc. But you get the idea.

The important thing here is that the two dangerous things, namely storing and validation of passwords and the setting and checking of login-cookies is all handled by ASP.Net for you so you can rely on that team knowing what they are doing. You are only doing the simple and safe things yourself.

So what about roles then?

git checkout step-4

This bit is lifted almost ad verbatim from Brock Allen.

In global.asax.cs insert this code:
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var ctx = HttpContext.Current;
if (ctx.Request.IsAuthenticated)
{
string[] roles = LookupRolesForUser(ctx.User.Identity.Name);
var newUser = new GenericPrincipal(ctx.User.Identity, roles);
ctx.User = Thread.CurrentPrincipal = newUser;
}
}

private string[] LookupRolesForUser(string name)
{
var repo = new AccountRepository(); // In the real world, you would probably use service locator pattern and call DependencyResolver here
var user = repo.FindByName(name);
if (user != null)
{
return user.Roles;
}

return new string[0]; // Alternatively throw an exception
}

Note that it may seem wasteful to go and find the roles on each request irrespecitive of whether the Authorize attribute asks for it. That is how the standard RoleProvider implementation works so you are no worse off, at least. But now that you have more control, you could quite easily implement a CurrentUserProvider which has caching so you can save database calls on each request.

Alternatively, if you don’t want to mess around with global.asax in this way, you could implement a custom RoleProvider and just implement GetRolesForUser if you really wanted.

And there you have it – it really is that simple to not use the Provider model for your users in MVC. Of course, you have to implement things like locking out users, reset password and so on. But as soon as you inherit from the base MembershipProvider you have to provide your own implementation of all those things anyway. And this way you are not bound by the constraints built into the abstraction.