I'm submitting a standard $.ajax() request like this:

<!-- language: lang-js -->
$.ajax({
    type: "GET",
    url: myUrl
    success: function(data) {
        $("#replace").html(data)
    },
    error: function (data) {
        console.warn(data);
    }
});

Or the same thing by attaching the handler to the ajax promise callbacks like this:

<!-- language: lang-js -->
$.ajax({
    type: "GET",
    url: myUrl
})
.done(function(data, status) {
    console.log(data);
})
.fail(function(data, status) {
    console.warn(status);
});

In both cases, or when using $.ajaxError() handler, the error/fail function is called when a HTTP status error is returned.


In my ASP.NET MVC project, I'm trying to return the proper HTTP Status Code, both for semantic reasons and to be caught by the right client side handler.

Attempt #1 - As suggested by this answer I've tried to return a HttpStatusCodeResult like this:

<!-- language: lang-cs -->
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    if (filterContext.HttpContext.Request.IsAjaxRequest())
    {
        filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Unauthorized, accessResult.AccessDeniedMessage);
        filterContext.HttpContext.Response.End();
    }
    else
    {
       base.HandleUnauthorizedRequest(filterContext);
    }
}

Attempt #2 - Alternatively, as suggested by this answer, I've tried returning a JsonResult and also setting the Response.StatusCode

<!-- language: lang-cs -->
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.Result = new JsonResult()
{
    Data = new { Error = "Unauthorized User" },
    JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.End();

In both cases, the response still comes back as 200 OK

AJAX Status Code Screenshot

Questions:

  • Am I correct about the semantics that I should be returning an AJAX response with a Unauthorized Status Code?
  • Is there somewhere else to set this value that I need to do also?
  • Is there some server level setting to allow non-200 status codes to be returned?

This question on Always success on ajax post with HttpResponseMessage 401 seems to be running into the same error, but doesn't propose a server side solution, instead just allowing the OK error status code and scraping the response to determine if an error occurred.

The offending issue was indeed the same as in Always success on ajax post with HttpResponseMessage 401. The response was returned successfully, but trapped a redirect to a form login

<!-- language: lang-none --> <pre><code><b>X-Responded-JSON</b>: {"status": 401, "headers": {"location":"http:\/\/localhost:50004\/Login?ReturnUrl=%2FClient"}}</code></pre>

Although that question doesn't seem to suggest a server side solution, instead simply relying on a parsing the error state on the client. Brock Allen suggests a server side fix in his post on Using cookie authentication middleware with Web API and 401 response codes:

Normally when using cookie authentication middleware, when the server (MVC or WebForms) issues a 401, then the response is converted to a 302 redirect to the login page (as configured by the LoginPath on the CookieAuthenticationOptions). But when an Ajax call is made and the response is a 401, it would not make sense to return a 302 redirect to the login page. Instead you’d just expect the 401 response to be returned. Unfortunately this is not the behavior we get with the cookie middleware — the response is changed to a 200 status code with a JSON response body with a message:

<!-- language: lang-js -->
{"Message":"Authorization has been denied for this request."}

I’m not sure what the requirement was for this feature. To alter it, you must take over control of the behavior when there is a 401 unauthorized response by configuring a CookieAuthenticationProvider on the cookie authentication middleware:

<!-- language: lang-cs-->
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
   AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
   LoginPath = new PathString("/Account/Login"),
   Provider = new CookieAuthenticationProvider
   {
      OnApplyRedirect = ctx =>
      {
         if (!IsAjaxRequest(ctx.Request))
         {
            ctx.Response.Redirect(ctx.RedirectUri);
         }
     }
   }
});

Notice it handles the OnApplyRedirect event. When the call is not an Ajax call, we redirect. Otherwise, we do nothing which allows the 401 to be returned to the caller.

The check for IsAjaxRequest is simply copied from a helper in the katana project:

<!-- language: lang-cs-->
private static bool IsAjaxRequest(IOwinRequest request)
{
   IReadableStringCollection query = request.Query;
   if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest"))
   {
      return true;
   }
   IHeaderDictionary headers = request.Headers;
   return ((headers != null) && (headers["X-Requested-With"] == "XMLHttpRequest"));
}