Why does AuthorizeAttribute redirect to the login page for authentication and authorization failures?

Viewed 97428

In ASP.NET MVC, you can mark up a controller method with AuthorizeAttribute, like this:

[Authorize(Roles = "CanDeleteTags")]
public void Delete(string tagName)
{
    // ...
}

This means that, if the currently logged-in user is not in the "CanDeleteTags" role, the controller method will never be called.

Unfortunately, for failures, AuthorizeAttribute returns HttpUnauthorizedResult, which always returns HTTP status code 401. This causes a redirection to the login page.

If the user isn't logged in, this makes perfect sense. However, if the user is already logged in, but isn't in the required role, it's confusing to send them back to the login page.

It seems that AuthorizeAttribute conflates authentication and authorization.

This seems like a bit of an oversight in ASP.NET MVC, or am I missing something?

I've had to cook up a DemandRoleAttribute that separates the two. When the user isn't authenticated, it returns HTTP 401, sending them to the login page. When the user is logged in, but isn't in the required role, it creates a NotAuthorizedResult instead. Currently this redirects to an error page.

Surely I didn't have to do this?

7 Answers

Add this to your Login Page_Load function:

// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)
    Response.Redirect("Unauthorized.aspx");

When the user is redirected there but is already logged in, it shows the unauthorized page. If they are not logged in, it falls through and shows the login page.

Unfortunately, you're dealing with the default behavior of ASP.NET forms authentication. There is a workaround (I haven't tried it) discussed here:

http://www.codeproject.com/KB/aspnet/Custon401Page.aspx

(It's not specific to MVC)

I think in most cases the best solution is to restrict access to unauthorized resources prior to the user trying to get there. By removing/graying out the link or button that might take them to this unauthorized page.

It probably would be nice to have an additional parameter on the attribute to specify where to redirect an unauthorized user. But in the meantime, I look at the AuthorizeAttribute as a safety net.

I always thought this did make sense. If you're logged in and you try to hit a page that requires a role you don't have, you get forwarded to the login screen asking you to log in with a user who does have the role.

You might add logic to the login page that checks to see if the user is already authenticated. You could add a friendly message that explains why they've been bumbed back there again.

In my case the problem was "HTTP specification used status code 401 for both "unauthorized" and "unauthenticated"". As ShadowChaser said.

This solution works for me:

if (User != null &&  User.Identity.IsAuthenticated && Response.StatusCode == 401)
{
    //Do whatever

    //In my case redirect to error page
    Response.RedirectToRoute("Default", new { controller = "Home", action = "ErrorUnauthorized" });
}
Related