Partager via


Securing your ASP.NET MVC 4 App and the new AllowAnonymous Attribute

2 March 2013 Update: Added security links
20 June 2012 Update: Cookieless Session and Authentication not supported in ASP.NET MVC.

   

Executive Overview

You cannot use routing or web.config files to secure your MVC application. The only supported way to secure your MVC application is to apply the Authorize attribute to each controller and use the new  AllowAnonymous attribute on the login and register actions. Making security decisions based on the current area is a Very Bad Thing and will open your application to vulnerabilities. 

Web.config-based security should never be used in an MVC application. The reason for this is that multiple URLs can potentially hit a controller, and putting these checks in Web.config invariably misses something.

There is a fundamental difference in protected resources between WebForms and MVC.  In WebForms, the resources you're trying to protect are the pages themselves, and since the pages exist on disk at a well-known path you can use Web.config to secure them.  However, in MVC, the resources you're trying to protect are actually controllers and actions, not individual paths and pages. If you try protecting the path rather than the controller, your application likely has a security vulnerability.

In MVC, by default all controllers + actions are accessible to all users, both authenticated and guest. To secure controllers or actions, the Authorize attribute has been provided (which can be applied globally as shown below).

This blog post is not a comprehensive list of security issues on ASP.NET MVC 4, it focuses on how the the AuthorizeAttribute applied in global.asax can be use to ensure all methods are authorized (except those explicitly white listed with the AllowAnonymous attribute. See the security section of the asp.net community site for more information.

ASP.NET MVC 3 introduced global filters, which allows you to add the AuthorizeAttribute filter to the global.asax file to protect every action method of every controller. (In MVC versions prior to MVC 3, it was difficult to enforce the AuthorizeAttribute attribute be applied to all methods except login/register. See my previous blog on security for details.) The code below shows how to add the AuthorizeAttribute filter globally.

 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new System.Web.Mvc.AuthorizeAttribute());
}

The problem with applying Authorize globally is that you have to be logged on (authorized) before you can log on or register.  In a previous blog, I suggested you create a custom AllowAnonymous attribute that you could apply to your login and register actions to explicitly opt out of authorization (that is, override the global application of Authorize on methods/controllers decorated with the attribute).  (Actually, Levi came up with the AllowAnonymous idea.)  ASP.NET MVC 4 includes the new AllowAnonymous attribute, you no longer need to write that code.  Setting the AuthorizeAttribute globally in global.asax and then whitelisting (That is, explicitly decorating the method with the AllowAnonymous attribute)  the methods you want to opt out of authorization is considered a best practice in securing your action methods.  Other approaches such as blacklisting methods (that is, decorating each method you want  with the AuthorizeAttribute attribute ), or creating a base controller with an [Authorize] attribute, and derive each controller (except the Account/Login controller) from that base class – have the problem that new controllers/methods are not automatically protected. See Securing your ASP.NET MVC 3 Application for more details.

When you create a new ASP.NET MVC 4 internet application, you’ll see the login and register methods decorated with this attribute:

  [AllowAnonymous]
 public ActionResult Login(string returnUrl)

 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public ActionResult Login(LoginModel model, string returnUrl)

 [AllowAnonymous]
 public ActionResult Register()

 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public ActionResult Register(RegisterModel model)
 To add a global authorization filter to your Web ApiController,  add the following line to the  
 Application_Start method in the global.asax file:
      GlobalConfiguration.Configuration.Filters.Add(new System.Web.Http.AuthorizeAttribute());

Notice the MVC controller authorize filter is from System.Web.Mvc while the ApiController authorize filter is from System.Web.Http.

Am I Safe Now?

ASP.NET applications configured for forms authentication use an authentication ticket that is transmitted between web server and browser either in a cookie or in a URL query string. The authentication ticket is generated when the user first logs on and it is subsequently used to represent the authenticated user.  It contains a user identifier and often a set of roles to which the user belongs. The browser passes the authentication ticket on all subsequent requests that are part of the same session to the web server. Along with the user identity store, you must protect this ticket to prevent compromise of your authentication mechanism.

Failing to properly protect forms authentication is a common vulnerability that can lead to the following:

  • Elevation of privileges. An attacker could elevate privileges within your application by updating the user name or the list of roles contained in the ticket prior to posting it back to the server. An attacker who can upload malicious code to your application can also successfully create and modify the form’s authentication tickets.

  • Session hijacking. An attacker could capture another user's authentication ticket and use it to access your application. There are a number of ways that this could happen:

    • As a result of a cross-site scripting vulnerability.
    • If the transport is not being protected using a security mechanism such as Secure Sockets Layer (SSL).
    • If the ticket is stored in the browser cache.
  • Session usage after sign-out. Even after the user has logged out of the application and the application has called FormsAuthentication.SignOut, the authentication ticket remains valid until its time-to-live (TTL) expires, so it can be used by an attacker to impersonate another user.

  • Eavesdropping. An attacker could look inside a form’s authentication ticket to obtain any sensitive information it contains and use this information to compromise your application.

  • Compromise of the user identity store. An attacker with access to the user identity store may obtain access to user names and passwords, either directly from the data store or by using a SQL injection attack.

To protect against these threats, you can apply the RequireHttpsAttribute  to the global filters collection in the global.asax file.

 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    filters.Add(new System.Web.Mvc.AuthorizeAttribute());
    filters.Add(new RequireHttpsAttribute());
}

Many web sites log in via SSL and redirect back to HTTP after you’re logged in, which is absolutely the wrong thing to do.  Your login cookie is just as secret as your username + password, and now you’re sending it in clear-text across the wire.  Besides, you’ve already taken the time to perform the handshake and secure the channel (which is the bulk of what makes HTTPS slower than HTTP) before the MVC pipeline is run, so redirecting back to HTTP after you’re logged in won’t make the current request or future requests much faster.  If you’re hosting static content (youtube for example), change your embedding to use HTTPS rather than HTTP. If you drop down to HTTP from HTTPS without correctly signing out (see https://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.signout.aspx ) your username + password is wide open. It's not enough to call FormsAuthentication.SignOut().

For information on setting up SSL on ASP.NET MVC, see my blog entry Better, Faster, Easier SSL testing for ASP.NET MVC & WebForms. IIS Express (the new default web server for Visual Studio 11) supports SSL.

Over-Posting Model Data

Suppose you have a blog and your comments are represented by this object from your ORM system:

 public class Comment
{
    public int ID { get; set; }      // Primary key
    public int BlogID { get; set; }  // Foreign key
    public Blog Blog { get; set; }   // Foreign entity
    public string Name { get; set; }
    public string Body { get; set; }
    public bool Approved { get; set; }
}

And this is the Create view for the comments to be entered by users:

  <div class="editor-label">
     @Html.LabelFor(model => model.Name)
 </div>
 <div class="editor-field">
     @Html.EditorFor(model => model.Name)
     @Html.ValidationMessageFor(model => model.Name)
 </div>

 <div class="editor-label">
     @Html.LabelFor(model => model.Body)
 </div>
 <div class="editor-field">
     @Html.EditorFor(model => model.Body)
     @Html.ValidationMessageFor(model => model.Body)
 </div>

The intention for comments is that they won't be approved by default. The blog owner will come along later and use the UI to approve all the non-spam comments.

What happens if the bad guy includes "Approved=true" in the form post? The model binder will happily set the Approved property to true. That's definitely not what you intended! It actually gets worse: if the bad guy guesses your entity object is called Blog, he might try posting values into fields like Blog.Body and actually be able to overwrite the body of the blog post. This is a potential disaster!

How can you prevent this? 

  1. The preferred approach is to pass a view model that doesn’t contain any fields you don’t want exposed  ( Blog and Approved in our sample. )
  2. You could use the Bind attribute, either on the Comment type or on the Comment action parameter, to indicate which properties are approved or disapproved for binding.  The "white-list" approach is preferred so that only the named properties can be bound to, rather than the "black-list" approach (which excludes specific properties, and allows binding to everything else). With the black-list approach, you have to remember to black-list new properties added to the model.
  3. Use the (Try)UpdateModel methods on Controller have overloads which accept white-/black-list parameter lists to tell the system which properties are eligible for binding.

Under-Posting Model Data

Under-posting is also a security risk. See Brad Wilson blog entry Input Validation vs. Model Validation in ASP.NET MVC which discusses security issues related to model validation. Although the blog was written in the ASP.NET MVC 2 timeframe, the security details apply to ASP.NET MVC 4 versions.

Mixing Windows Authorization with Forms Authentication

The ASP.NET team doesn’t support using mixed-mode authentication in an application.  If you search the web, you’ll find blog posts on how to do this, but please note that the techniques discussed in these blog posts are discouraged by the team.  The reason this is discouraged is that it is very difficult to reason about security from a correctness point of view, and there are trivial attacks against such a setup that can allow malicious clients to masquerade as an authenticated user. If a supported/secure approach to using mixed-mode authentication is important to you, make a request on our UserVoice page.

Cookieless Session and Authentication

ASP.NET MVC does not support cookieless session and authentication and we actively discourage it. Cookieless  sessions are vulnerable to session hijacking attacks.

Deploy a Secure ASP.NET MVC app with Membership, OAuth, and SQL Database to a Windows Azure Web Site

ASP.NET MVC 4 Security section of content map

Here’s some good StackOverflow (SO) links to Levi’s responses to area security questions.

This blog post is not a comprehensive list of security issues on ASP.NET MVC 4, it focuses on how the the AuthorizeAttribute applied in global.asax can be use to ensure all methods are authorized (except those explicitly white listed with the AllowAnonymous attribute. See the security section of the asp.net community site for more information.

In addition to blogging, I use Twitter to-do quick posts and share links. My Twitter handle is: @RickAndMSFT

Comments

  • Anonymous
    March 25, 2012
    Does the global Authorize filter work with the new Web Api in ASP.NET MVC 4?

  • Anonymous
    March 29, 2012
    As an alternative to using attributes (Authorize/AllowAnonymous) to secure your MVC application, I would recommend taking a look at FluentSecurity (http://www.fluentsecurity.net). It's free and open source!

  • Anonymous
    March 30, 2012
    Awesome article, very helpful! Just one thing - please, fix a typo in this sentence -  "(the new default web browser for Visual Studio 11)". Should it be  "(the new default web server for Visual Studio 11)"?

  • Anonymous
    April 03, 2012
    Michael :Does the global Authorize filter work with the new Web Api in ASP.NET MVC 4? I updated the blog to show this. Thanks for asking. Kristoffer Ahl : look at FluentSecurity (http://www.fluentsecurity.net - Thanks reav : Typo - fixed, thanks

  • Anonymous
    April 10, 2012
    Hi, thanks for your comment. I’m glad you find this collection useful. Thanks

  • Anonymous
    July 19, 2012
    Best article on securing our applications.. Very much helpful... Thank you.

  • Anonymous
    July 29, 2012
    dear rick, do you know if its planned to release a new forms authentication system with asp.net 4.5? otherwise, do you know in which place its possible to write ms in order to ask for functionalities and features? brgds!

  • Anonymous
    August 05, 2012
    I have authentication in my web.config which works perfectly in MVC and with Controllers without needing to set any of the above???  I am using VS 2012 Express for Web and .Net 4.0.

  • Anonymous
    February 25, 2013
    Thank you for making this so clear.  Have been struggling with how to setup an MVC4 application that will consume a AWS.  This solves the MVC part.

  • Anonymous
    March 05, 2013
    Thanks, nice post. I'd like to see something similar around ASP.NET Web API where cookies aren't an option and we have to use some flavour of OAuth / HMAC / trusted client token / etc. What do you see as the best options in this space?

  • Anonymous
    April 29, 2013
    I'm curious, with the [Authorize(Roles = "Admins")] on the controller... will this use an AzMan provider if everything was setup properly for AzMan (i.e. Connection string / role manager in config)? We currently use AzMan to manage all of our roles.

  • Anonymous
    June 20, 2013
    Thanks Rick. This is exactly what I was looking for. Many times MSDN does not give me what I need. Kudos

  • Anonymous
    June 23, 2013
    Question on using FormsAuthentication. I created a new controller in the same Controllers folder as the AccountController named ForgotPasswordController. I have the [Authorize] attribute on the public class ForgotPasswordController and the only action in the controller has the [AllowAnonymous] and also the [HttpGet] attribute applied. When I run the application I am unable to navigate to the /ForgotPassword/ResetPassword and I get redirected to the login page. So, my question is, why can't I use the Authorize and AllowAnonymous with forms authentication on any controller except the AccountController?

  • Anonymous
    July 04, 2013
    <a href="www.baysoftsoftware.com/">Estate Agency Software</a> Thanks for sharing great information with us. Keep it up........

  • Anonymous
    October 26, 2013
    Hi I created a brand new mvc4 application from Internet template added filters.Add(new System.Web.Mvc.AuthorizeAttribute());  to RegisterGlobalFilters method. Now It generates 401 unauthorized when I hit F5. It goes to login action and it marked with [AllowAnonymous] attribute. why I can not go to Login view?

  • Anonymous
    March 24, 2014
    Hi Rick, What is best practice for session storage kind of scenario? User logs in you application and I want to store its personal info in session and use is accross the pages in application. Regards, Mangesh Jadhav

  • Anonymous
    March 26, 2015
    Hi Rick, How can I disable authentication in public pages such as home page?

  • Anonymous
    April 27, 2015
    Hi Rick_Anderson, Thanks for such a nice and informative article. I an very new to MVC and i am making a web application on MVC 4 and WEB api 2. Please suggest me how i should proceed for adding authentication and authorization feature. I have to make my web api's also secure. Please reply....

  • Anonymous
    July 08, 2015
    Why when we remove the roles authorize does not work automatically, because I need to log out and then a login for work? The problem is that the roles are stored in the cookie. I have to find some solution to update the cookie. When do I remove a roles directly in the database the cookie is outdated. I think I have update the cookie to each User request. The example I'm using is on github https://github.com/aspnet/Musi...

  • Anonymous
    July 08, 2015
    Rick, Why when we remove the roles authorize does not work automatically, because I need to log out and then a login for work? The problem is that the roles are stored in the cookie. I have to find some solution to update the cookie. When do I remove a roles directly in the database the cookie is outdated. I think I have update the cookie to each User request. The example I'm using is on github https://github.com/aspnet/Musi...