Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forms Authentication and POST requests from AJAX

We have an ASP.NET app protected by forms authentication. The app uses MS AJAX heavily to call its web-services.

When the forms authentication times out, and a GET-request happens - all is fine (the user is redirected to a login page).

BUT when the forms authentication times out and a POST-request happens (ajax) - no redirect happens, instead the app returns "401 unathorized" and the browser prompts for username and password (not a login form, but a browsers built-in dialog). Of course entering ANY username/password never helps.

How do I handle this?

UPDATE: After looking with firebug, I just found out that regular POST requests redirect to login fine, it's only web-service calls that throw "401 Unauthorizes". The difference between a regular request and web-service is URL. Which is "page.aspx" for a regular post-request and "service.asmx/MethodName" for webservices...

like image 842
Alex from Jitbit Avatar asked Dec 10 '09 06:12

Alex from Jitbit


2 Answers

Ok, answering my own questin.

After looking into this issue and researching a bit more I found that when a web-app is protected by Forms-Authentication and the user is not authenticated, this is what happens:

  • If it's a GET-request - the user is redirected to the login page.
  • If it's a POST-request to a page - the user is redirected to the login page.
  • If it's a POST-request to a web-service - the user gets 401-unauthorized

Thats how ASP.NET works

And if a web-service is called by AJAX (xmlHttpRequest object) and returns 401 - of course the browser shows a pop-up login box.

Now, what should you do is add some code to Application_PostAuthenticateRequest that will prevent throwing 401 for webservices.

protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
    if (Request.RequestType == "POST" //if its POST
        && !User.Identity.IsAuthenticated //if user NOT authed
        && !HasAnonymousAccess(Context) //if it's not the login page
        )
    {
        //lets get the auth type
        Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
        SystemWebSectionGroup grp = (SystemWebSectionGroup)config.GetSectionGroup("system.web");
        AuthenticationSection auth = grp.Authentication;
        //if it FORMS auth
        if(auth.Mode== AuthenticationMode.Forms)
        {

            //then redirect... this redirect won't work for AJAX cause xmlHttpRequest can't handle redirects, but anyway...
            Response.Redirect(FormsAuthentication.LoginUrl, true);
            Response.End();

        }
    }
}
public static bool HasAnonymousAccess(HttpContext context)
{
    return UrlAuthorizationModule.CheckUrlAccessForPrincipal(
        context.Request.Path,
        new GenericPrincipal(new GenericIdentity(string.Empty), null),
        context.Request.HttpMethod);
}
like image 77
Alex from Jitbit Avatar answered Oct 21 '22 18:10

Alex from Jitbit


I see two solutions:

(1) "Heart beat" mechanism. On each page include a script that will "ping" the server by some dummy ajax request, like:

<script>
   setInterval(ping, 60000); // based on comment by John
   function ping()
   {
       $.get('/do/nothing');
   }
</script>

This way the session shouldn't expire as long as the browser window is open.

(2) On each ajax request check the status of the response. If the response has "401 unauthorized" code (or any other code different that 200), that means that the session expired and instead of loading the response into some dialog box in the page redirect the user to login page.

Conclusion based on comments:

The best solution would be to combine the two above mechanisms. Heartbeat mechanism will help to keep the session alive as long as the page is displayed in the browser. But in doesn't guarantee that for sure. The connection to the server can be broke and reopened when the session is expired. So you should check the response status anyway.

like image 1
PanJanek Avatar answered Oct 21 '22 17:10

PanJanek