If you are developing a web application which requires authentication or security features not included in the regular ASP.NET membership feature, you might decide to implement these features yourself. But it seems as if the first instinct of many ASP.NET MVC developers is to do this by customizing their Controllers, because they’ve decided that AuthorizeAttribute can’t possibly serve their needs. They will decide that the way to do this is to write some sort of parent Controller type which examines the currently logged-in user when an action executes and changes the result of the action based on who they are. Others will try to re-implement AuthorizeAttribute without ever examining the source code for the original.
These are very bad approaches, for two important reasons:
- They don’t work. If your action result is cached by ASP.NET, then the action will not even run the next time it is requested. AuthorizeAttribute handles this correctly. Code you write in a Controller cannot handle this. Code you write in a custom action filter could work, if you cut and paste the implementation from AuthorizeAttribute. But AuthorizeAttribute is unsealed, so why cut and paste, when you can subtype?
- The modularity is wrong. You should aim to develop MVC sites which can be used with any authentication (or role) provider, whether it is ASP.NET membership, domain authentication, OpenId, or a custom membership provider. Wiring authentication concerns into a Controller makes this extremely difficult.
I’ve already hinted at the correct solution for the problem: If you cannot find an out of the box membership provider which does what you need, then look harder. If you still can’t find one, write your own. Here is a video and an article on how to do it. Similarly, the role provider can also be customized. In other words, the correct extensibility point for membership in an MVC site is exactly the same as the extensibility point for membership in a non-MVC ASP.NET site.
AuthorizeAttribute does a very simple thing in a somewhat complicated way. The simple thing it does is to tie the MVC framework into the existing ASP.NET membership system, which is quite customizable. That’s why I am skeptical about claims that AuthorizeAttribute can never serve someone’s needs. The implementation is more complex than one might imagine is required on first consideration because it must both be thread safe and cooperate with caching, which makes attempting to re-invent it at a different level a doubly bad idea.
Although AuthorizeAttribute doesn’t do very much, you may decide that it’s specific behavior is not correct for your project. In that case, it is generally a better idea to subtype AuthorizeAttribute instead of re-inventing your own. This allows you to preserve desirable behavior (like cooperating with caching) while customizing the behavior you need. You will need to override the AuthorizeCore method, taking careful note of the comments (in the MVC source code for the inherited method) on thread-safety.
One final word about customizing security: Your general approach should be to use proven, off the shelf, actively maintained solutions and customize as little as possible. If the problem you are trying to solve is not strictly security-related (like adding a custom field to a user profile), then do not alter (and potentially compromise) your security in order to solve it. Instead, find a more appropriate solution, like storing the information elsewhere, such as in an ASP.NET profile or your own DB. Similarly, don’t use something which is not intended to have hard security, like Session, for security purposes. Attackers can and do hijack session information. Doing security correctly is hard, and the consequences of small mistakes can be high. Even experts in the field make mistakes which can take years to uncover. Even if you follow to the letter everything which is known about implementing good security, today, your implementation may be obsolete tomorrow.