Looking at how the ASP.NET MVC Authorize interacts with ASP.NET Forms Authorization
Background: The Authorization flow in a default ASP.NET MVC Internet Application
ASP.NET MVC includes an [Authorize] attribute, which when placed on any controller actions will forbid unauthorized access. The AuthorizeAttribute allows you to specify a list of roles or users, like this:
You can also place the AuthorizeAttribute on a controller, in which case it will apply to all actions in the controller. Attempting to access an action secured by the AuthorizeAttribute when you're not logged in will take you to a standard LogOn screen, with a link to register if you don't already have an account.
You don't have to write this code, because the ASP.NET MVC 3 Internet Application template includes a basic AccountController which implements the following actions (along with the associated models and views):
- LogOn
- Register
- ChangePassword / ChangePasswordSuccess
How does the [Authorize] attribute redirect me to Log On?
The AuthorizeAttribute is an ActionFilter, which means that it can execute before the associated controller action. The AuthorizeAttribute performs its main work in the OnAuthorization method, which is a standard method defined in the IAuthorizationFilter interface. Checking the MVC source code, we can see that the underlying security check is really just looking at the underlying authentication information held by the ASP.NET context:
If the user fails authentication, an HttpUnauthorizedResult ActionResult is returned, which produces an HTTP 401(Unauthorized) status code. If it weren’t for ASP.NET Forms Authentication, an HTTP 401 status code would be sent to the browser, which would show the browser’s default login prompt. You can get an idea using the (very useful) httpstat.us site, browsing to http://httpstat.us/401
These 401 prompts are uniformly hideous, but the real problem is that they aren’t able to authenticate against membership systems running on your server. That’s why most websites prefer to display a nice login web form (as shown in the first login screenshot). As mentioned earlier, that’s where ASP.NET Forms Authentication comes into play.
How ASP.NET Forms Authentication turns MVC’s 401 errors into a redirect to a login page
Your application’s web.config contains the settings you’re most likely to need to edit. There are a lot of other default values living in the Framework’s web.config file. On my default installation, that file is found at C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config. If you crack that open, you’ll see that there are 14 default HTTP Modules installed. One of them is the FormsAuthenticationModule. The code for the FormsAuthenticationModule is found in System.Web.dll.
The FormsAuthenticationModule OnLeave method has one purpose: it checks for 401 responses and turns them into redirects. Before it redirects, though, it figures out a return url so so after completing login successfully, the Account / Logon action redirects to the originally requested page.
The login url is defined in the application’s web.config, as shown below.
You can of course change this login url to any custom location that makes sense for you.
The AccountController LogOn method is a standard ASP.NET MVC controller action. There’s not a lot of code here, since it’s leveraging the underlying membership provider mechanism in ASP.NET.
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is all configurable
It’s nice that this is all provided in an ASP.NET MVC the Internet Application and Intranet Application templates. In simple cases, adding authorization doesn’t require any additional code or configuration.
Equally nice, though, is that you can change any of those parts:
- The AccountController (as well as the associated Account models and views) is a standard ASP.NET MVC controller, which is pretty easy to modify. You can see an example of this in the MVC Music Store tutorial, in which we migrate an anonymous shopping cart to a user account on registration / login.
- The authorization calls work against the standard ASP.NET Membership provider mechanism, as defined in your web.config <authorization> setting. You can switch providers, or write your own.
- The AuthorizeAttribute is a standard authorization attribute, implementing IAuthorizeFilter. You can create your own authorization filters.