Thoughts on ASP.NET MVC Authorization and Security

It is only a matter of time in developing most websites that you'll need to implement a way of restricting access to parts of the site. In MVC, the 'Authorize' attribute handles both authentication and authorization. In general, it works well, with the help of extension to handle AJAX calls elegantly, and to distinguish between unauthorized users and those who are not logged in.

As developers, and users of web-based applications and services, we’re constantly trying to balance two different requirements. On one hand there’s connectivity and full-time traceability of activity-you leave clues of yourself through social networks and logs of web sites. On the other hand, there is the need for privacy and security aimed at ensuring that only authorized users can perform certain actions or access certain services. Speaking in general, the problem of web services is not so much the functional requirement of having to implement a solid and reliable security barrier as doing that in a context in which full traceability and open connectivity is a nonfunctional requirement.

In ASP.NET Web Forms and ASP.NET MVC you have effective tools for implementing software barriers around critical parts of the codebase. You can use the technique of isolating critical modules from the rest of the application and making it reachable via HTTPS or specific authentication tokens. In ASP.NET in particular, it is common to put ASPX pages in a folder under the control of a custom web.config file that redirects unauthenticated users to a predefined login page.

In ASP.NET MVC you restrict access to methods using the Authorize attribute. In particular, you use the Authorize attribute when you want to restrict access to an action method and make sure that only authenticated users can execute it. Simple and effective? Yes, but still there are a few things around it that deserve some more thought.

The Authorize Attribute

In ASP.NET MVC, any incoming request is bound to a controller/method pair and served. This means that once the request matches a supported route and is resolved to controller and method, it gets executed no matter what. Put another way, any public method defined on the controller class can be run if only the user calls the right URL. Sounds like a possible security hole? Yes and no. you could regard it as a possible security hole but it is certainly no more dangerous than having a hidden ASPX page in a Web Forms web site. Nobody outside the team is expected to know it, but the page is there and will respond to requests.

To take security seriously, you should separate methods (or pages) that should never be invoked from the outside from those that can be invoked only by authorized users. Finally, you should define what you intend by an “authorized user.”

In ASP.NET MVC, you use the Authorize attribute every time you have a controller method that only “known” users can invoke. Here’s how to use the Authorize attribute.

You can apply the Authorize attribute to individual methods as well as the controller class as a whole. If you add the Authorize attribute to the controller class, then any action methods on the controller will be only available to authenticated users.

The Authorize attribute is inheritable. This means that you can add it to a base controller class of yours and thereby ensure that any methods of any derived controllers are subject to authentication.

NOTE: In general, any public method on a Controller class can be invoked via a valid URL. If you want to avoid that, then you should either change the visibility attribute of the method and make it protected or private or just mark it with the NonAction attribute.

Allowing Anonymous Callers

The Authorize attribute is easy to use if you have only a few methods on a controller class reserved to authenticated users. In a situation in which all methods but a few are subject to authorization, you should add the Authorize attribute to all secured methods. A smarter approach to take in this case is the following:

  1. You add the Authorize attribute to the controller class. In doing so, all action methods on the controller class are subject to authentication.
  2. You apply a new attribute to only the few methods that don’t require authentication-the AllowAnonymous attribute.

When applied to a method, the AllowAnonymous attribute instructs the ASP.MVC runtime to accept and process the call even if the caller is not authenticated. The scenario when the AllowAnonymous method comes handy is when you apply Authorize at the class level and then need to enable free access some methods – in particular, login methods.

Handling Authorization for Action Methods

Authentication is the process of acquiring the credentials of the requesting user. Authorization is instead the process of verifying that the acquired credentials are valid for the request action method. In light of this, the name Authorize chosen for the attribute is a bit misleading. It explicitly refers to authorization, but its first effect is triggering the authentication process and the display of a login form.

The Authorize attribute, however, is not limited to authentication. As the name actually suggests, it also supports some basic forms of authorization. The attribute supports a couple of parameters through which developers can restrict the execution of the action method only to certain user names and/or users with a given role. Here’s an example:

If a user is not authenticated, or doesn’t have the required user name and role, then the Authorize attribute prevents access to the method and redirects the user to the login URL. When both Roles and Users are set, the effect is combined and only users with that name and in that role are authorized. In the example above, only users Bob and Alice having the role of Admin can have access to the method.

Not Authorized or Just Not Logged In?

The Authorize attribute is the only security-related action attribute supported by ASP.NET MVC. It handles authentication and authorization, but it sometimes misses some details. In particular, when the attribute fails on a request and redirects the user to the configured login page, can you reliably say why is it happening? Is it because an anonymous access has just been attempted? Or is it rather because the user who attempted the access doesn’t have enough privileges?

When the Authorize attribute fails, the response is a HTTP 401 (unauthorized) status code. While this status code may work most of the time, it is still a bit generic at a second, more thoughtful, look. More specifically, a HTTP 401 status code leaves the developer clueless about what really happened: whether the user is not logged in or was logged-in but did not have the rights to invoke a given action method. In both cases, in fact, the attempt to call the action method redirects the user to the login page.

The HTTP documentation doesn’t have status codes specific for unauthenticated and unauthorized users. HTTP 401-unauthorized-generically refers to requests that are unauthorized for whatever reason. HTTP 403-forbidden-refers to requested resources that are forbidden to the current user. Should you distinguish between 401 and 403? How important is in your application knowing why the request fails?

As mentioned, most of the time 401 works fine regardless of whether you get it because you didn’t provide valid credentials or you provided valid credentials with insufficient privileges for that resource. If you need to distinguish between the two, then one thing you can do is create an enhanced attribute class that manages to return a clear message to the user. As shown below, this class is a simple extension to system-provided AuthorizeAttribute class.

In the new class, you override the OnAuthorization method and run some extra code to check whether you’re getting an HTTP 401 message. If this is the case, you then check whether the current user is authenticated and redirect to your own error page (if any). You have View and Master properties to configure the target error view with instructions for the user.

So what’s the net effect of the new attribute class?

If an anonymous user attempts to access a page that requires authentication she is then simply redirected to the configured login page. Otherwise, if the request failed because the authenticated user lacks valid permissions, then the user receives a friendly error page that gives more information about what happened. Using the new attribute couldn’t be easier:

The custom attribute doesn’t change the basic fact that the bound method returns an ActionResult type. However, it returns HttpUnauthorizedResult if the user is anonymous; it returns a ViewResult object pointing to a friendly error page if valid credentials have been provided but referencing an account with insufficient permissions.

Client-side Authentication

If your page is largely based on JavaScript-say, you’re building a single-page application-then you do nearly everything from the client, including the authentication of users. This means that you add some script that grabs credentials and silently calls into a remote endpoint. The endpoint does the authentication/authorization work and returns a response in some form. You process the response within a JavaScript callback and decide what your next step in the application is.

If you’re worried about security of the data that travels over the wire, well, consider that using JavaScript is neither more nor less dangerous than using a more canonical server-side page. In any case, content typed in the text box-yes, credentials-travels as clear text unless you set up a HTTPS connection.

Imagine now you have a page with a bit of JavaScript but not entirely based on JavaScript. Suppose for example that you manage authentication on the server through redirects to login pages. Your page has triggers for remote calls that return data or, why not, markup ready to be injected in DIV elements. Imagine now that a user clicks somewhere and places an Ajax call to the server. Imagine also that the authentication cookie has expired. Subsequently, the server returns an HTTP 302 status code, which redirects the user to the login page. Unfortunately, in an Ajax scenario, it’s the XMLHttpRequest object-not the browser-that handles the 302 request. As a result, the login page will likely be inserted in any DOM location where the original response was expected.

You can fix this by adding an extra IF to the code of the custom LoggedOrAuthorizedAttribute class. You add the following to the beginning of the internal CheckIfUserIsAuthenticated method.

Explained, it means that if the attribute is going to return a code different from HTTP 200 then you also check if the request is coming over Ajax. If so, you just return an empty response but explicitly set a 401 status code that can be handled from JavaScript code. Here’s an example of some markup with a button that may cause a login redirect:

And here’s the code for the failed function:

In the end, when the user clicks the button and no valid cookie is found, the consequence is the update of the DIV: no full page refresh and no undesired side effects.

Summary

Blocking calls to sensitive methods is at the foundation of web development. In ASP.NET MVC, the Authorize attribute does most of the work but, in some cases, it may be worth some extension. Note also that ASP.NET MVC ensures that the Authorize attribute takes precedence over output caching. In particular, the output caching layer returns any cached response for a method subject to Authorize only if the user is authenticated and authorized.

These notes work for any version of ASP.NET MVC. However, the upcoming ASP.NET MVC 5 promises to have some more features, especially authorization filters, to give developers a chance to filter calls on methods on a per-user basis with more comfort than just a declarative attribute as in Authorize. Stay tuned!