2

Asp.net MVC5 and WebApi2 have Authorize attributes on controllers that let you filter on roles. For example:

[Authorize(Roles="HomePageUser")]
public ActionResult Home(){
    return View();
}

But this emits a 401 status code if the user is authenticated but not in the correct role. My question is simply: is this the best approach or should I write a filter that sends 403 if they are authenticated but in the wrong role?

To see why this is an issue, consider: the user who is not in role HomePageUser logs in and tries to go to the Home page. The Authorize filter emits a 401 code which the Owin context then intercepts and turns into a 302 redirect to the login page. But, the user is in fact logged in and no amount of logging in can rectify the fact that the user's identity lacks the permissions to view the page.

In practical terms, this all means frustration for the users because the automatic redirecting never gives them any information about why they can't see the page--they are likely to assume they typed in their password incorrectly.

It seems to me that the key failure here is that the Authorize filter lied--the user was not unauthorized but forbidden, and it should have issued a 403.

Here's the code I have in mind to fix this. For MVC actions:

using System.Web.Mvc;

public class AuthorizeOrForbidAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new HttpStatusCodeResult(403);
        }
        else
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
    }
}

and for Web Api actions:

using System.Web.Http;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.Properties;

public class AuthorizeOrForbidAttribute: AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, actionContext.RequestContext.Principal.Identity.Name+" does not have permissions for this request.");
        }
        else
        {
            actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Authorization has been denied for this request.");
        }
    }
}

From there it is easy enough to redirect these status codes to the appropriate error pages depending on what you want the user to see.

Is this a good approach? I'm worried because I'm wondering if there's a good reason Microsoft didn't do this in the first place.

Matthew
  • 4,149
  • 2
  • 26
  • 53
  • That seems like the correct way to do it as the Authorize attribute by default behaves as you describe. See this answer -> http://stackoverflow.com/questions/2578756/how-to-make-authorize-attribute-return-custom-403-error-page-instead-of-redirect – Jeff Bobish Apr 11 '16 at 18:47

1 Answers1

0

But, the user is in fact logged in and no amount of logging in can rectify the fact that the user's identity lacks the permissions to view the page.

If they can't log in with an account that allows them to access the resources in question, then why are they going there?

Either they're deliberately trying to do something that shouldn't work, in which case I wouldn't have that much sympathy for their frustration, or there are links or other navigational features suggesting they can do it, which is a bigger problem that will lead to frustration either way.

Beyond that, both approaches have their proponents. The upside of the forbidden approach is being able to direct the user to what they can do. The upside to login prompts is being able to login with a different account and continue if the resource was one you could access that way.

You can have features of both if you include a login form on your forbidden error page, to cover that case.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • good points. I'm thinking of two main use points: 1)a user shares a link with someone who lacks permissions, and 2)debugging--if I have stray links or screw up permissions somewhere, 403 page is more likely to make it's way back to me so I can fix it. – Matthew Apr 12 '16 at 11:15
  • On point 1, if I follow a link from someone that makes me log in I either log in (I know I need to do that) or I realise I can't get to it. There's not a big win either way here, but I'd lean to 401. On the second point that's a matter of your logging; if you can detect that someone got a 401 while logged in, you can log that as different to the case of just getting one because they weren't logged in yet. – Jon Hanna Apr 12 '16 at 11:42
  • 1
    A web server should return the correct status code. This isn't a user interface issue, this is about correct configuration of your system. This becomes important for instance when ADFS is involved, and you get stuck in redirect loops because it thinks you still need to log in. – Nacht Jul 18 '17 at 02:00
  • @Nacht that near-tautology just restates the question. Of course the webserver should return the correct status code. Saying that doesn't answer the question as to which is the correct status code. – Jon Hanna Jul 18 '17 at 09:24
  • 2
    Clearly it should be a 403. Your answer implies it doesn't matter because the user is trying to hack or there are broken links. The correct answer can be found here: https://stackoverflow.com/a/5844884/983442 – Nacht Jul 18 '17 at 22:58
  • @Nacht I don't see it as clear that you shouldn't ask the user to log in with different credentials. Please provide an answer demonstrating that. – Jon Hanna Jul 19 '17 at 00:26
  • @Nacht also my answer implied no such thing. – Jon Hanna Jul 19 '17 at 00:27