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?

+1  A: 

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

(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 plan on removing the link based on authorization as well (I saw a question on here about that somewhere), so I'll code an HtmlHelper extension method up later.
Roger Lipscombe
I still have to prevent the user from going directly to the URL, which is what this attribute is all about. I'm not too happy with the Custom 401 solution (seems a bit global), so I'll try modelling my NotAuthorizedResult on RedirectToRouteResult...
Roger Lipscombe
+2  A: 

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.

It's my feeling that most people don't tend to have more than one identity for a given web app.If they do, then they're smart enough to think "my current ID doesn't have mojo, I'll log back in as the other one".
Roger Lipscombe
Although your other point about displaying something on the login page is a good one. Thanks.
Roger Lipscombe
+5  A: 

Add this to your Login Page_Load function:

// User was redirected here because of authorization section
if (User.Identity != null && User.Identity.IsAuthenticated)

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.

Alan Jackson
Page_Load is a webforms mojo
@Chance - then do that in the default ActionMethod for the controller that is called where FormsAuthencation has been setup to call.