phuzi phuzi - 3 months ago 20
Ajax Question

Weird AJAX redirect 401 issue with IIS

Should I remove this question?
I figured out what the issue was and it wasn't IIS... See my answer below for the outcome.

Original Question
I'm working on an ASP.Net MVC app and run in to a weird issue with URL Rewrite redirects and AJAX requests.

I've added the following rewrite rule to my Web.config in the root site.

<rewrite>
<rules>
<rule name="Account" stopProcessing="true">
<match url="^SubApp/Account/(.*)$" />
<action type="Redirect" url="Account/{R:1}" redirectType="Found" />
</rule>
</rules>
</rewrite>


Everything seems to work ok if I use a
Permanent
or
Temporary
redirectType
in the config but fails with a
HTTP Error 401.0 - Unauthorized
IIS error page .

When I make an normal GET request via the browser to an action that would trigger this rule e.g.
https://some-site/SubApp/Account/Settings
then I get a 302 Found and the location header is set to the expected URL
https://some-site/Account/Settings
and the appropriate page is rendered.

However when I make a GET request via JQuery's AJAX i.e.
$.get('https://some-site/SubApp/Account/Settings')
the returned Response status code is
401 Unauthorized
but it still has the appropriate location header.

The content of the response is a standard IIS
HTTP Error 401.0 - Unauthorized
error page.

Weirdly everything seems to work ok if I use either the
Permanent
or
Temporary
redirect types in the config but fails only with
Found
.

/SubApp
is a separate application that sits below the root site at
/
.

What's going on?

Screenshots

redirectType="Permanent"

Permanent Redirect

redirectType="Found"

Found Redirect

redirectType="Temporary"

Temporary Redirect

As you can see from the screenshots the only difference is the
redirectType
specified in the
Web.config
.

As you can see the redirects are happening as expected with exception to the
Found
redirect type which I would expect to get a
302 - Found
response redirecting to the same URL as the others.

Answer

Ahhhh, you know when you don't think about something for a while and you get hit with sudden inspiration... Well it happened last night and I found this little nugget put in place to "fix" MVC's insistance on redirecting AJAX requests when authentication fails...

protected void Application_EndRequest()
{
    var context = new HttpContextWrapper(Context);
    // MVC retuns a 302 for unauthorized ajax requests so alter to request status to be a 401
    if (context.Response.StatusCode == 302 && context.Request.IsAjaxRequest() && !context.Request.IsAuthenticated)
    {   
        context.Response.Clear();
        context.Response.StatusCode = 401;
    }
}

And, unsuprisingly, context.Request.IsAuthenticated is always false as it appears to get reset by the redirect .

Updated this, with a little help from Branislav Abadjimarinov's blog post on the subject.

protected void Application_EndRequest()
{
    var context = new HttpContextWrapper(Context);
    // MVC returns a 302 for unauthorized ajax requests so alter to request status to be a 401

    if (context.Response.StatusCode == 302 && context.Request.IsAjaxRequest())
    {
        //Unfortunately the redirect also clears the results of any authentication
        //Try to manually authenticate the user...
        var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
        if (authCookie != null)
        {
            var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            if (authTicket != null && !authTicket.Expired)
            {
                var roles = authTicket.UserData.Split(',');
                HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(authTicket), roles);
            }
        }

        if (!context.Request.IsAuthenticated)
        {
            context.Response.Clear();
            context.Response.StatusCode = 401;
        }

    }
}

And it all works as expected.

Only question is should I remove this question?