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.