views:

465

answers:

4

If you're wondering what the "Race Condition" is, its a flaw in a system whereas it's highly dependent on timing. See the wiki here for more info.

So, the condition i have is relating to Facebook Connect and implementing a Single-Sign-On service with an ASP.NET 4.0 Web Application (Forms Based Authentication - IIS7).

The process itself (including Logout) is working nicely, but.....

Here's scenario where it's not exactly working 100%:

  1. User logs into Facebook.
  2. User navigates to my website.
  3. User is not logged in automatically (should be though).
  4. User refreshes the page, and is logged in automatically.

When i breakpoint the code in step 3 - the Facebook Cookies are not yet there (in the HttpContext.Current.Request.Cookies).

But when the page is reloaded (step 4) - the Facebook Cookies are there.

To me, it could be a number of things:

I'm not sure if its just a case of Facebook has not yet granted access to my application to access the cookies (delay in cross-domain handshake - xd_receiver.htm), or an issue with cross-domain cookies itself and the ASP.NET Page lifecycle.

Anyone else dealt with this problem?

It's not the "be-all-and-end-all", but it's annoying (and not great from a user perspective).

EDIT:

Ok ive noticed something strange now. If i log into Facebook (via Facebook), and wait say 20 seconds, then go to my website, it STILL does not log me in. Only after the second load does it log me in. So maybe its not a timing issue - why does it need 2 refreshes to be able to read the cookies?

To clear up some confusion - Facebook sets the cookies (that the user is logged into Facebook), and my website reads these cookies.

This happens on every page request (logic in a user control which is on every single page)

protected void Page_PreRender(object sender, EventArgs eventArgs)
{
   if (FacebookUser.IsAuthenticated) // static property, checks HttpContext.Request.Cookies
   {
        // log them into my website
   }
}

So, on first refresh - there is nothing in HttpContext.Request.Cookies.

On second refresh, they are there.

I think the reason for this is because the FB.Init is executed on the client-side on every page request. This is what initializes the cookies. So when you first go to my website (after logging into Facebook), on the server side (where i check the cookies), this function has not yet been run.

So i think im fighting a losing battle here (trying to access a cookie server-side, that is set client-side).

EDIT 2:

Here's my init code (run on window.load):

FB.init('myapikey', 'xd_receiver.htm', null);

I'm now trying to do something like this:

FB.init('myapikey', 'xd_receiver.htm', null);
FB.getLoginStatus(function(response) {
  if (!response.session) {
      return false;
  }
  else {
    window.location.reload();
  }
});

But am getting a JavaScript error - "FB.getLoginStatus" is not a function. =(

This is because im using the following JavaScript library: http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/en_US

Whereas other people say to use this one: http://connect.facebook.net/en_US/all.js

I was looking at the doco for the new JS API.

For reference to other's who stumble to this thread, here is the doco for the 'old' JS API: http://developers.facebook.com/docs/reference/oldjavascript/

So i've got this problem solved by using FB.Connect.get_status() and a window reload if the user is authenticated.

Thanks for everyone's help.

This is my "solution" to the problem if anyone else cares:

window.onload = function() { 
                FB.init('{0}', 'xd_receiver.htm');
                FB.ensureInit(function() {
                   FB.Connect.ifUserConnected(onUserConnected, onUserNotConnected); 
                });
            };

            function onUserConnected() {
                alert('connected!');
                window.location.reload();
            }

            function onUserNotConnected() {
                alert('not connected');
            }

Of course, you should check the Forms Authentication cookie before doing window.location.reload(), otherwise the page will just keep refreshing. =)

A: 

Since you can't control the entry point to the race condition (the user login to facebook) there is a limited number of options you can do:

  1. Add a delay at step nr 3 ( maybe a few miliseconds of sleep will give Facebook enough time to sign your use in). This will have the nasty effect of a partial fix: it will work on "most" cases, and it will induce a delay on all your logins.

  2. If authentication fails at step 3, THEN wait a few miliseconds (test to find the right ammount), then attept to authenticate again. Only then display the not-authenticated page.

  3. If this is a page lifecycle issue (unlikely) add an extra page/redirect for your login page. Meaning your process will look like this:

    a) user logins to facebook
    b) user navigates to your site (cookie not set yet)
    c) user is redirected to login page (cookie may be set). If user is not authenticated yet wait 100ms then redirect to step b) (only once).
    d) user is logged in

Radu094
The problem is, i check the cookies on every page request. So if i add a delay, this will cause every page to be delayed. Not ideal. Thanks for the answer though. Remember when i say i "automically log them in", im not redirecting them to any page, when they refresh ANY page on my site, it checks the cookies and logs them in.
RPM1984
I've updated my question. Seems its nothing to do with timing at all, my website for some reason needs 2 refreshes to register the cookies.
RPM1984
+1  A: 

This is not a "race condition", and can be better characterized as a side effect of how Browser based Authentication works. There are three "actors" involved here:

  1. Your Application
  2. Facebook
  3. The User

When the User visits your Application, Facebook has not been involved in the process yet. This means you don't know if the User is a Facebook User who has already authorized with your Application, because Facebook hasn't had a chance to tell you yet. This is why you don't get any cookies in the first request.

Now when your Application responds to this request, it includes the Facebook JavaScript in it. The JavaScript SDK will ping Facebook in the background by using an IFrame (if you tell it to using the status: true option or more explicitly), and get a response from it based on the User currently signed into Facebook (if any). At this point, if a session was returned (that is, there is a signed in User who has authorized your Application in the past), the Facebook JavaScript SDK will set a cookie (if you tell it to do so using the cookie: true option).

So that's the logic behind why the first request does not get the cookies. In addition to setting the Cookies, you can also get notified of this via JavaScript Events which will allow you to take certain actions. Most common action is to simply reload the page and allow the server to notice the freshly set cookies and render a appropriate signed in view. But another option is to use Ajax and do something fancier that does not involve reloading the entire page.

As to why it only happens for you only on the second page load, I'm not sure. But this is how it is designed to work :)

NOTE: I did not go into the advanced usage scenario, which involves a full page redirect to as such undocumented but supported end point (the same one the JavaScript SDK uses) which provides the illusion of fixing this issue. I'd highly recommend using the JavaScript SDK since it provides a much simpler and generally more performant solution at a small usability cost. But if you care enough I could elaborate on that too.

daaku
I'm still new to FBC, but what do you mean by "autorization callback"? The user logs into Facebook on, then goes to my site. My site checks the cookies on every page request to see if they are authenticated. There is no ajax, it happens on Page_Load...if cookies are there, check them with my membership schema, log them in using Forms Auth. The problem is if they go straight to my site after logging in, the cookies arent there. If they say wait 5 seconds, its all good. Am i doing the auto-login the wrong way?
RPM1984
Maybe I'm not understanding how you're going about this. Are you using the JavaScript SDK to do the authorization? That is, who sets the cookies your application is reading?
daaku
In this scenario, Facebook sets the cookies (because im logging in via Facebook). Then i'm simply reading these cookies in my application (httpcontext.request.cookies). I only use the JS SDK for 2 things - registering the cross-domain receiver file (FB.init - xd_receiver), and for the logout (FB.Connect.logout). I've also noticed something strange about this 'race condition' - im updating my question.
RPM1984
Facebook cannot set cookies on your domain in a direct way, it is the JS SDK that does this. What you are likely seeing is that the first request to your application does not have the cookies -- because the JS SDK hasn't had a chance to run yet. I'll edit my answer since I don't think I have enough space here to explain :)
daaku
Thanks for your help. In a word - FB sucks. =). So many doco's, api's, .net ports, and they change it everyday.
RPM1984
A: 

Hi,

I have the exact same issue. Could you figure out the reason for this? Surprisingly, it works for me on my production server but not on localhost (I have changed my /etc/host to refer to localhost as peta.edu because of the localhost related issues)

Multiple refresh does not work for me either. But, if I manually click on the URL bar and hit enter, it works just fine.

Regards,

Nithin.

Nithin
As i said above, the cookie is only received on your website when you make the FB.Init call - which is on the client side (after page is rendered). So you cant "get a client side cookie on the server", as the cookie has not yet been created. Just use some ajax smarts to refresh the parts of the page you care about.
RPM1984
A: 
David Semeria
Its not a Facebook issue really, its the nature of Forms Authentication and the HTTP Request/Response lifecycle. FB.Init is called on the client side (after page is rendered). At this point, we do know the user is authenticated - the cookie is created (as you say). But the page has already been loaded (as an unauthenticated user). By refreshing the page, you pass these "new" cookies with the HTTP Request, which enables the single sign on. Its the only way you can overcome this - however i have since changed my code and instead of doing a full refresh i do ajax updates.
RPM1984