When it was first developed, System.Web.Mvc.AuthorizeAttribute was doing the right thing -
older revisions of the HTTP specification used status code 401 for both "unauthorized" and "unauthenticated".
From the original specification:
If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials.
In fact, you can see the confusion right there - it uses the word "authorization" when it means "authentication". In everyday practice, however, it makes more sense to return a 403 Forbidden when the user is authenticated but not authorized. It's unlikely the user would have a second set of credentials that would give them access - bad user experience all around.
Consider most operating systems - when you attempt to read a file you don't have permission to access, you aren't shown a login screen!
Thankfully, the HTTP specifications were updated (June 2014) to remove the ambiguity.
From "Hyper Text Transport Protocol (HTTP/1.1): Authentication" (RFC 7235):
The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource.
From "Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content" (RFC 7231):
The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it.
Interestingly enough, at the time ASP.NET MVC 1 was released the behavior of AuthorizeAttribute was correct. Now, the behavior is incorrect - the HTTP/1.1 specification was fixed.
Rather than attempt to change ASP.NET's login page redirects, it's easier just to fix the problem at the source. You can create a new attribute with the same name (AuthorizeAttribute
) in your website's default namespace (this is very important) then the compiler will automatically pick it up instead of MVC's standard one. Of course, you could always give the attribute a new name if you'd rather take that approach.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
From the readme word doc for RC1 (not indexed by google)
ASP.NET Compiler Post-Build Step
Currently, errors within a view file are not detected until run time. To let you detect these errors at compile time, ASP.NET MVC projects now include an MvcBuildViews property, which is disabled by default. To enable this property, open the project file and set the MvcBuildViews property to true, as shown in the following example:
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MvcBuildViews>true</MvcBuildViews>
</PropertyGroup>
Note Enabling this feature adds some overhead to the build time.
You can update projects that were created with previous releases of MVC to include build-time validation of views by performing the following steps:
- Open the project file in a text editor.
- Add the following element under the top-most
<PropertyGroup>
element:
<MvcBuildViews>true</MvcBuildViews>
- At the end of the project file, uncomment the
<Target Name="AfterBuild">
element and modify it to match the following:
<Target Name="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
</Target>
Best Answer
UPDATE (Jun 2015): @daniel-lidström has correctly pointed out that you should not use Response.Redirect in an ASP.NET MVC application. For more information about why, please see this link: Response.Redirect and ASP.NET MVC – Do Not Mix.
UPDATE (Sep 2014): I'm not sure when HandleUnauthorizedRequest was added to the AuthorizeAttribute, but either way I've been able to refine the AuthorizeRedirect code into something smaller and simpler.
Original Answer Below (still fully functional)
I've left this answer here as it still gives you an insight as to how the Authorization pipeline works.
For anyone still landing here, I've edited Ben Scheirman's answer to automatically redirect to an unauthorized page when the user is logged in but not authorized. You can change the redirect path using the name parameter RedirectUrl.
EDIT: I've made the solution thread-safe thanks to the advice of Tarynn and MSDN