views:

776

answers:

4

I have some action methods behind an Authorize like:

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(int siteId, Comment comment) {

The problem I have is that I'm sending a request through AJAX to Comment/Create with

X-Requested-With=XMLHttpRequest

which helps identify the request as AJAX. When the user is not logged in and hits the Authorize wall it gets redirected to

/Account/LogOn?ReturnUrl=Comment%2fCreate

which breaks the AJAX workflow. I need to be redirected to

/Account/LogOn?X-Requested-With=XMLHttpRequest

Any ideas how that can be achieved? Any ways to gain more control over what happens when Authorization is requested?

A: 

Instead of using the authorize attribute, I've been doing something like the following.

public ActionResult SomeCall(string someData)
{
    if (Request.IsAjaxRequest() == false)
    {
        // TODO: do the intended thing.
    }
    else
    {
        // This should only work with AJAX requests, so redirect
        // the user to an appropriate location.
        return RedirectToAction("Action", "Controller", new { id = ?? });
    }
}
Jarrett Meyer
I got that part worked out already. My problem is that that method has an Authorize in front of it and the redirection of the Authorize looses the capacity to be checked for AJAX like you did.
J. Pablo Fernández
A: 

I think the right way to handle this would be in your Javascript making the AJAX call.

If the user needs to be authorized (or authenticated as your code implies) and isn't, you should inform them and maybe not allow them to try and comment in the first place.

However, if that doesn't suit your needs. You could try and write your own authorize action filter, maybe inheriting from the one that comes with the MVC framework but redirects how you want it to. It's fairly straightforward.

Lewis
I want them to try to comment and participate in the site and only prompt for log in when necessary. There's no way in JavaScript code that I can know whether the user is logged in without making an AJAX call to check that first and then trying to perform that function.I liked your idea of doing another action filter, but after playing with that a little bit it doesn't seem to control where the user is redirected to. Do you know for sure it does? Any other hints?
J. Pablo Fernández
My bad, now I've found it. Thanks for the pointer in the right direction Lewis.
J. Pablo Fernández
+2  A: 

Thanks to Lewis comments I was able to reach this solution (which is far from perfect, posted with my own comments, if you have the fixes feel free to edit and remove this phrase), but it works:

public class AjaxAuthorizeAttribute : AuthorizeAttribute {
    override public void OnAuthorization(AuthorizationContext filterContext) {
        base.OnAuthorization(filterContext);
        // Only do something if we are about to give a HttpUnauthorizedResult and we are in AJAX mode.
        if (filterContext.Result is HttpUnauthorizedResult && filterContext.HttpContext.Request.IsAjaxRequest()) {
            // TODO: fix the URL building:
            // 1- Use some class to build URLs just in case LoginUrl actually has some query already.
            // 2- When leaving Result as a HttpUnauthorizedResult, ASP.Net actually does some nice automatic stuff, like adding a ReturnURL, when hardcodding the URL here, that is lost.
            String url = System.Web.Security.FormsAuthentication.LoginUrl + "?X-Requested-With=XMLHttpRequest";
            filterContext.Result = new RedirectResult(url);
        }
    }
}
J. Pablo Fernández
Great example. But how to test base.OnAuthorization(filterContext); invocation?
A: 

Recently I ran into exactly the same problem and used the code posted by J. Pablo Fernández with a modification to account for return URLs. Here it is:

public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    override public void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
        // Only do something if we are about to give a HttpUnauthorizedResult and we are in AJAX mode. 
        if (filterContext.Result is HttpUnauthorizedResult && filterContext.HttpContext.Request.IsAjaxRequest())
        {
            // TODO: fix the URL building: 
            // 1- Use some class to build URLs just in case LoginUrl actually has some query already. 
            HttpRequestBase request = filterContext.HttpContext.Request;
            string returnUrl = request.Path;
            bool queryStringPresent = request.QueryString.Count > 0;
            if (queryStringPresent || request.Form.Count > 0)
                returnUrl += '?' + request.QueryString.ToString();
            if (queryStringPresent)
                returnUrl += '&';
            returnUrl += request.Form;
            String url = System.Web.Security.FormsAuthentication.LoginUrl +
                         "?X-Requested-With=XMLHttpRequest&ReturnUrl=" +
                         HttpUtility.UrlEncode(returnUrl);
            filterContext.Result = new RedirectResult(url);
        }
    }
}
SlimShaggy