views:

33

answers:

3

My ASP MVC (1.0) website has a default login page (based on OpenId - but that shouldn't make a different). It works fine when AuthorizedAttribute is on the Action/Controller.

However, I have AJAX requests coming in as well. Here is what I do with them:

if (Request.IsAjaxRequest())
{ 
  if (Request.IsAuthenticated)
  {
    // Authenticated Ajax request
  }
  else
  {
    // Non-authenticated Ajax request.
    Response.StatusCode = (int)HttpStatusCode.Unauthorized;
    return Json(new { response = "AUTHENTICATION_FAILED" });
   }
}

The problem is if I set the Response.StatusCode to Unauthorized, the request is redirected to my login page which is not good for Ajax requests.

Any suggestions for this issue is appreciated.

+4  A: 

This is a common problem.

The Authorize attribute returns a Http 401 Unauthorized response. Unfortunately, however if you have FormsAuthentication enabled, the 401 is intercepted by the FormsAuthenticationModule which then performs a redirect to the login page - which then returns a Http 200 (and the login page) back to your ajax request.

The best alternative is to modify your authorization code to return a different Http status code - say 403 - which is not caught by the formsAuthenticationModule and you can catch in your Ajax method.

Clicktricity
Sounds good. Will try it, thanks. Is this changed in anyway in MVC 2 or 3?
Khash
Unfortunately not. It's stil the same in MVC2 and 3. I have raised it as an issue with the MVC team, but FormsAuthenticationis not really within their area of responsibility
Clicktricity
A: 

One way of solving this is to add a text to uniquely identify the login page and use it in AJAX call back to redirect to a login page again. Here is a sample code using jQuery global callbacks....

$(document).bind("ajaxComplete", function(event, response, ajaxOptions) {
    if (response.status == 200 && response.responseText.match(/LOGIN_PAGE_UNIQUE_KEY/)) {
        self.location = "/web/login?timeout=1";
        return false;
    }

});
Teja Kantamneni
This is not really a good solution since it requires all AJAX calls to be changed and is not using the Login page framework in ASP MVC.
Khash
@Khash You don't need to change all the ajax calls. this is a global binding which can be included in one common page
Teja Kantamneni
A: 

You can make your own authorize filter that inherent from the framework one, and override the function that write to the response when the user is not authorized, not setting the status code if the request is from ajax. Something like this:

public class MyAutorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
            filterContext.Result = new JsonResult() { Data = new { response = "AUTHENTICATION_FAILED" } };
        else
            filterContext.Result = new HttpUnauthorizedResult();
    }
} 

And now on your action use the new attribute

[MyAutorize]
public ActionResult myAction()  
{
  if (Request.IsAuthenticated) // You should not need to ask this here
  {
      // Authenticated Ajax request
  }
  else
  {
    // Non-authenticated Ajax request.
     Response.StatusCode = (int)HttpStatusCode.Unauthorized;
     return Json(new { response = "AUTHENTICATION_FAILED" });
  }
}
David Martinez
Maybe I failed to convey my question properly. It doesn't matter if I do this in a custom Attribute or the code, the fact is setting the StatusCode to Unauthorize redirects the client to the login page that is not an AJAX one.
Khash