Customizing Authorize attribute
Authorize
The Authorize attribute available in MVC framework helps to restrict users from accessing secured controllers and actions. When a user who is not authenticated or authorized tries to access the controller or action that is decorated with Authorize attribute generates a 401 response and if the site has forms authentication enabled then the user will be redirected to the login page. The problem with this behavior is the authenticated user (but not authorized) also get redirected to the login page, mostly developers like to show an access denied page in those case.
This article is mostly a kind of tip that describes how we can achieve that by extending the built-in Authorize attribute. Note that, the solution provided here doesn't handle thread safety. For a better solution please refer to Ben Cull's answer in this thread.
HandleUnauthorizedRequest
The Authorize attribute implements IAuthorizationFilter and exposes a bunch of virtual methods (AuthorizeCore, HandleUnauthorizedRequest, OnAuthorization, OnCacheAuthorization) that we can override. OnAuthorization method is the main method that calls the other virtual methods, it calls the AuthorizeCore to know whether the user is authenticated and authorized to access the controller or action. The method returns false if the authorization fails and is it is then the OnAuthorization calls the HandleUnAuthorizeRequest to take appropriate action.
The advantage of splitting the complete authorization process into pieces of function is we can override only the ones required and reusing the others else we have to do the complete implementation. The default implementation of HandleUnAuthorizeRequest is it returns 401.
This is the HandleUnAuthorizeRequest code that I copied from codeplex.
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext) { // Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs. filterContext.Result = new HttpUnauthorizedResult(); }
Listing 1. HandleUnauthorizedRequest default implementation
It's just a one line code that instantiates and sets an HttpUnauthorizedResult to the filterContext's result. The filter returns 401 and it is perfectly valid as per HTTP standards but how the Forms authentication module reacts to this 401 is kind of inconvenience. It redirects the user to login page even-though the user may already logged in. A better approach would be taking the user to login page if he is not at all authenticated and if he is authenticated but not authorized then redirecting him to a custom access denied page.
If the site has not forms authentication enabled then the programmer can control the action that has to be taken by handling the Application_Error event in Global.asax.cs. So this problem is only with the ASP.NET applications that uses forms authentication.
One way we can overcome this problem is creating a custom authorize filter and overriding the HttpUnauthorizedResult and check if the user is not authenticated then set 401 else set a redirect action result to a custom page. One thing we are moving from the HTTP spec is we are not setting 401 when the user is authenticated but not authorized though it's not correct but I see that would be fine compared to the inconvenience.
Ok, enough theory! Let's complete the work and it is as simple as it is!
public class CustomAuthorize: AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { if(!filterContext.HttpContext.User.Identity.IsAuthenticated) { base.HandleUnauthorizedRequest(filterContext); } else { filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new{ controller = "Error", action = "AccessDenied" })); } } }
Listing 2. Overriding HandleUnauthorizedRequest
We are checking if the user is not authenticated and if it is then do the default way else redirect the user to the AccessDenied action of an Error controller.
That's it! Enjoy!!
Like to hear comments from you :)