Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Always success on ajax post with HttpResponseMessage 401

I always get statusCode=200 on ajax post at client side, while servers answers with HttpStatusCode.Unauthorized.

My controller code:

public class AccountApiController : ApiController
{
    public HttpResponseMessage Login(HttpRequestMessage request, [FromBody]LoginViewModel loginModel)
    {
        return request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Unauthorized login.");
    }
}

My ajax code:

$.ajax({
    url: '/api/accountapi/login',
    type: 'POST',
    data: data
    })
    .done(function (object, status, xhr) {
        alert("Success: " + xhr.status + " : " + xhr.statusText);
    })
    .always(function (object) {
        $("#output").text(JSON.stringify(object, null, 4));
    });

Result: alert with text Success: 200 : OK and output window with:

{
    "Message": "Unauthorized login."
}

So, I can get text error message, but i need to get HttpStatusCode to handle error statements. Help me, please.

More information about this issue and elegant solution from Brock Allen: http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/

like image 899
Valin Avatar asked Dec 18 '13 15:12

Valin


2 Answers

It might be that your page is redirecting to a login page by the forms authentication module as soon as you return HttpStatusCode.Unauthorized.

From MSDN:

All unauthenticated users are denied access to any page in your application. If an unauthenticated user tries to access a page, the forms authentication module redirects the user to the logon page specified by the loginUrl attribute of the forms element.

The login page, or whatever page it's being redirected to, is then served with status code 200.

like image 150
Magnus Engdal Avatar answered Nov 15 '22 17:11

Magnus Engdal


To elaborate on Valin's comment, and inline Brock Allen's solution.

The clue lies in the returned OK response which internally traps the redirect to a form login:

X-Responded-JSON: {"status": 401, "headers": {"location":"http:\/\/localhost:50004\/Login?ReturnUrl=%2FClient"}}

If you'd like to fix on the server, instead of scraping the response for this internal error state, you can use the solution from Brock Allen's article 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:

{"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:

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:

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"));
}
like image 37
KyleMit Avatar answered Nov 15 '22 18:11

KyleMit