Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make ASP.NET Web API respond 403 or 401 appropriately?

I am using ASP.NET Web API. And I do like the ability to add attributes to specify levels of access to the API controllers like this:

[Authorize]
public IEnumerable<Activity> Get()

So far so good, but when I use roles the concept breaks apart.

[Authorize(Roles = "Manager")]
public IEnumerable<Activity> Get()

My user may have logged on to the system a while back, and then at some point they hit a resource that is "forbidden" to them. There is no sense in the user attempting to log on again. Since their rightful account does not have access to that URL. But currently they get a 401 (unauthorized) instead of a 403 (forbidden) as if they had logged on with the wrong account. But the user only has one account, and it is not intended that users ask for an account that belongs to someone else.

Has anyone else dealt with this problem? Does anyone know how to fix this? I am more than willing to write the code to fix this, but I am currently at a loss on where to start.

like image 792
Arturo Hernandez Avatar asked Mar 31 '13 20:03

Arturo Hernandez


People also ask

How do I authorize Web API?

Web API provides a built-in authorization filter, AuthorizeAttribute. This filter checks whether the user is authenticated. If not, it returns HTTP status code 401 (Unauthorized), without invoking the action. You can apply the filter globally, at the controller level, or at the level of individual actions.

When can you return unauthorized?

If authentication fails, a 401 Unauthorized response should be returned. Authentication can fail for a lot of reasons: bad password, an expired API token, something in the application changed, etc. For example, a user changing their password often invalidates any oauth tokens issued on behalf of that user.

How does Webapi bind ASPX parameter?

When Web API calls a method on a controller, it must set values for the parameters, a process called binding. By default, Web API uses the following rules to bind parameters: If the parameter is a "simple" type, Web API tries to get the value from the URI.

HOW CAN REST API be implemented in ASP NET?

Select the Visual C# | Web project type from the project type tree view, then select the ASP.NET MVC 4 Web Application project type. Set the project's Name to ContactManager and the Solution name to Begin, then click OK. In the ASP.NET MVC 4 project type dialog, select the Web API project type. Click OK.


2 Answers

Reading Parv's suggestion, I created the following custom filter called [WebApiAuthorize].

The key is the HandleUnauthorizedRequest() method. When code is executing inside this method, it is because the user is unauthorized "for some reason".... so now we just determine out "why".... and then either:

  1. Call base method for default behavior (return 401).... or....
  2. Return our own response with a 403.

As you can see, it returns 403 when appropriate (authenticated, but not authorized).

public class WebApiAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext ctx)
    {
        if (!ctx.RequestContext.Principal.Identity.IsAuthenticated)
            base.HandleUnauthorizedRequest(ctx);
        else
        {
            // Authenticated, but not AUTHORIZED.  Return 403 instead!
            ctx.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
        }
    }
}

To use, just throw the custom filter on a controller or action like this.....

[WebApiAuthorize(Roles = "YourRoleA,YourRoleB")]
public class AdminController : ApiController
{
    public List<Admin> GetAdmins()
    {
        ...
    }
} 
like image 88
ClearCloud8 Avatar answered Oct 06 '22 04:10

ClearCloud8


I did a bit of research and I coded a solution for me. I found two different Authorize attributes one on System.Web.Mvc and a second one in System.Web.Http. The first one applies to a regular MVC4 app and the second one to the WebAPI portion of MVC4 used for web services including RESTful interfaces. So I used the second one.

I decided to look at the Authorize Attribute Source code at codeplex. And I found this:

    protected virtual bool IsAuthorized(HttpActionContext actionContext)
    {
        if (actionContext == null)
        {
            throw Error.ArgumentNull("actionContext");
        }

        IPrincipal user = Thread.CurrentPrincipal;
        if (user == null || !user.Identity.IsAuthenticated)
        {
            return false;
        }

        if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
        {
            return false;
        }

        if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
        {
            return false;
        }

        return true;
    }

It is easy to see how authentication and access are conflated by the fact that they both have the same effect of returning false.

Here is a new AuthorizeAttribute I wrote that returns 403 when the user or roles don't match. That way you avoid getting a native log on window. It includes the following code.

        if (!IsAuthorized(actionContext))
        {
            HandleUnauthorizedRequest(actionContext);
        }

        if (!IsAllowed(actionContext))
        {
            HandleForbiddenRequest(actionContext);
        }
like image 24
Arturo Hernandez Avatar answered Oct 06 '22 05:10

Arturo Hernandez