Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you return an HTTP response from an AuthorizeAttribute without throwing an exception?

I'm using an AuthorizeAttribute on various controllers which may need to return 403 or 429 (too many requests) based on certain attributes of the request itself. I implemented it entirely within a custom OnAuthorization implementation then threw a new HttpResponseException with the appropriate response code if necessary. Worked fine on my machine...

At scale (many thousands of requests a minute), this implementation sucks to the point where it was crashing the site. Moving the same logic into the controller action itself and just returning an appropriate HttpResponseMessage works beautifully in terms of perf so it seems that the expense of throwing the exception in OnAuthorization is the root cause of the perf issues.

I like the idea of implementing this in an attribute I can use to decorate multiple controllers and actions and I vehemently dislike moving even small amounts of logic into controller actions that are then repeated many times. Is it possible to return the appropriate HTTP status from an annotation without throwing an exception? Even if it's not inheriting from AuthorizeAttribute, decorating code in this fashion would be far preferable.

Edit: This is Web API 2, not MVC

like image 804
Troy Hunt Avatar asked Aug 20 '16 01:08

Troy Hunt


2 Answers

As you have discovered, throwing exceptions is expensive. The trick in this case is to override the response in the attribute. As MVC and WebAPI are different (at least prior to MVC6) there are two distinct methods.

MVC

Setting the AuthorizationContext.Result allows you to effectively override what action is being performed. Setting this value will prevent the action it is attached to from running at all:

public override void OnAuthorization(AuthorizationContext filterContext)
{
    if(Throw403)
    {
        filterContext.Result = new HttpStatusCodeResult(403);
    }
}

WebAPI

Very similar but you must instead set the HttpActionContext.Response property. One handy feature of this, is that you get a nice enum for the HTTP status code:

public override void OnAuthorization(HttpActionContext actionContext)
{
    if(Throw403)
    {
        actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
    }
}
like image 122
DavidG Avatar answered Nov 05 '22 10:11

DavidG


How about custom message handlers where you could respond back even before hitting the controller? This happens early in the pipeline.

Edit - pasting relevant info from website

A delegating handler can also skip the inner handler and directly create the response:

public class MessageHandler2    :DelegatingHandler
{
 protected override    Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    // Create the response.
    var response = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent("Hello!")
    };

    // Note: TaskCompletionSource creates a task that does not contain a delegate.
    var tsc = new TaskCompletionSource<HttpResponseMessage>();
    tsc.SetResult(response);   // Also sets the task state to "RanToCompletion"
    return tsc.Task;
   }
}

And this is how you register the handler

  public static class WebApiConfig
    {
  public static void        Register(HttpConfiguration config)
{
    config.MessageHandlers.Add(new MessageHandler1());
    config.MessageHandlers.Add(new MessageHandler2());

    // Other code not shown...
}
}

Ref here: http://www.asp.net/web-api/overview/advanced/http-message-handlers

sorry for the bad formatting, this is the best I could do via mobile

like image 1
Developer Avatar answered Nov 05 '22 12:11

Developer