OdeToCode IC Logo

Perils of the MVC4 AccountController

Monday, September 24, 2012
The final release of ASP.NET MVC 4 brought some changes to how membership works in the MVC Internet Project template. While pre-release versions of MVC 4 all used the traditional ASP.NET membership providers for forms authentication, the final release relies on the WebSecurity and OAuthWebSecurity classes from the Web Matrix and Web Pages assemblies. Behind the scenes, the SimpleMembershipProvider and the ExtendedMembershipProvider, as well as DotNetOpenAuth are all at work.
These changes are a good move, because many web sites no longer want to store user credentials locally. Instead, they want to use OAuth and OpenID so someone else is responsible for keeping passwords safe, and people coming to the site have one less password to invent (or one less place to share an existing password). With these changes it is easy to authenticate a user via Facebook, Twitter, Microsoft, or Google. All you need to do is plugin the right keys.

However, there are a few issues to be aware of if you build an application on top of the existing code in the AccountController.

1. The InitializeSimpleMembershipAttribute

The InitializeSimpleMembershipAttribute (let’s call it ISMA) is part of the code generated by the MVC 4 Internet template. ISMA exists to provide a lazy initialization of the underlying providers, as well as potentially create the database for storing membership, roles, and OAuth logins. You’ll find it decorating the AccountController as an action filter, so it can jump in and initialize all the bits before the controller starts to interact with the security related classes.

[Authorize]
[InitializeSimpleMembership]
public class AccountController : Controller
{
// ...
}

As an action filter, ISMA hooks into OnActionExecuting to perform the lazy initialization work, but this can be too late in the life cycle. The Authorize attribute will need the providers to be ready earlier if it needs to perform role based access checks (during OnAuthorization). In other words, if the first request to a site hits a controller action like the following:

[Authorize(Roles="Sales")]

.. then you’ll have an exception as the filter checks the user’s role but the providers aren’t initialized. We’ll talk about concerns over the exception message details later.

My recommendation is to remove ISMA from the project, and initialize WebSecurity during the application start event.

2. Usage of the Entity Framework

The MVC 4 Internet project template uses EF code-first classes to manage user profiles, so it includes a DbContext derived class (UsersContext) and a UserProfile entity, both of which are defined in the Models\AccountModel.cs file.

If you plan on customizing the UserProfile class, make sure you let the application create the database for you. You can see the database creation code in the ISMA also.

If you create a database yourself, make sure to include all the tables needed for the WebSecurity class. There are not scripts published that I have found, but you can always generate DDL scripts after the application runs.

webpages membership tables

If you create an empty database and run the application to let WebSecurity create its own tables, it will not include any of your user profile customizations (it will only do that for you if the database doesn’t exist).

There is a strange tension in a new application because it’s not clear who is responsible for getting all the pieces to work together. WebSecurity can create the database schema it needs to operate, but can miss creating something you add to the UserProfile entity, which is code you can customize, but you wouldn’t know that at a first glance.

If you are using the Entity Framework for an application, my recommendation is to remove the DbContext derived class from AccountModel.cs and take ownership of the UserProfile class. You’ll need to fix places in the AccountController that are looking to use the context class, but replace those pieces of code with your own context class.

If you are not using the Entity Framework, you’ll want to remove the DbContext derived class in any case.

3. Impact on EF Migrations

Because the Internet project template includes an Entity Framework DbContext class in the project, if you add your own DbContext to the project and try to enable EF migrations, you’ll be greeted with an error.

PM> enable-migrations
More than one context type was found in the assembly.
To enable migrations for [Context], use Enable-Migrations –ContextTypeName [Context].

The problem is easy to solve. As the error states, you can use the –ContextTypeName flag to specify your context class name. Note that you can only have migrations for one context in a project, so if you want to have migrations for both contexts you’ll need to move one to a different project. Again, my recommendation is to just remove the existing UsersContext the Internet project template creates, and take ownership of the user profile in your own context.

4. Impact on Seeding Data

The previous ASP.NET membership providers were easy to work with from the Seed method in an EF migrations class. With this new approach you need to perform some additional configuration steps. I covered these steps in a previous post: “Seeding Membership and Roles in ASP.NET MVC 4”.

5. Impact of WebMatrix Assemblies

It’s possible to run into an exception message like the following (like when the ISMA code runs too late):

You must call the "WebSecurity.InitializeDatabaseConnection" method before you call any other method of the "WebSecurity" class. This call should be placed in an _AppStart.cshtml file in the root of your site.

We don’t use an _AppStart.cshtml file in ASP.NET MVC, so look to add code to Application_Start in global.asax.cs instead.

6. Impact of the WebMatrix Style

A few people have written me to ask if the AccountController created by the Internet application template is the future of ASP.NET MVC coding. A representative sample appears below:

public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Home");
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}

// If we got this far, something failed, redisplay form
return View(model);
}

Specifically, people are asking about the number of static method calls through WebSecurity and trying to figure out the impact on testing and extensibility, as well as the impact on impressionable young minds who might read too much into the code.

I’ve assured everyone the future is not full of static methods. Or, at least not my future.

Where Are We?

In some upcoming posts we’ll explore an alternative approach to membership, roles, and OAuth in ASP.NET MVC 4, and see if there is an approach that is simple, testable, and extensible enough to work with more than a relational database for storage.