11

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.

phuzi
  • 12,078
  • 3
  • 26
  • 50
  • Can you add the code. – Mairaj Ahmad Apr 28 '15 at 04:15
  • Could you add screenshot of network traffic for both cases (working / not working)? – Marian Polacek Apr 28 '15 at 07:43
  • I can do but it's quite a `$.get("/BMS/Account/Settings")`. I'll update the question with some screenshots... – phuzi Apr 28 '15 at 07:49
  • I suspect you are getting a not same origin problem. It is considered better practice to use url helpers and tilda slash for virtual urls letting the framework workout the issue. This way you're unlikely to get an erroneous origin based 401. – Dave Alperovich Apr 28 '15 at 12:08
  • @DaveAlperovich - If that was the case why would it work for the `Permanent` and `Temporary` redirects? – phuzi Apr 28 '15 at 12:12

2 Answers2

5

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?

phuzi
  • 12,078
  • 3
  • 26
  • 50
0

Take a look at this Cannot handle 302 redirect in ajax and why? [duplicate], it looks like the web browser sees the Found-302 and performs an action on it.

Community
  • 1
  • 1
Brian from state farm
  • 2,825
  • 12
  • 17
  • I'll double check tomorrow, but the session is fully authenticated and I see both a 301 with a location header then a subsequent request by the browser to the new location with a 200 status response when it is set to `Permanent` I see exactly the same except a 307 status code for the first response when `redirectType` is set to `Temporary`. When it is set to `Found` I get a 401 but weirdly the location header is set to the resource I was expecting the redirect to point to. I'm not trying to handle the redirect manually and the location header is pointing at the correct resource. – phuzi Apr 27 '15 at 21:23
  • Brian, I may be missing something but the link youposted seems to be about not redirecting unauthorized requests to an authorization page bc page redirects cannot happen in ajax. If so, my understanding of OP issue is something completely different. – Dave Alperovich Apr 28 '15 at 12:05