Using Facebook Graph API with the JS SDK in a canvas page iFrame in Safari is broken
C

2

5

So I'm trying to use the Graph API with the Facebook JS SDK and I'm getting the following error in Safari:

"OAuthException: An active access token must be used to query information about the current user."

I suspected it had to do with the fact that Safari is very strict with x-domain cookie setting and so I tried it in Firefox with cookie option set to false in FB.init(). I indeed found that I was getting the same error for my FB.api() requests.

FB.init({
  appId:  "<%= app_id %>",
  status: true, // check login status
  // We cannot rely on this cookie being set in an iframe. I found this
  // out because the Graph API was not working in Safari.
  // cookie: true, // enable cookies to allow the server to access the session
  xfbml:  true, // parse XFBML
  channelUrl: window.location.protocol + '//' + window.location.host + '/fb/canvas/channel.html'
});

So I'm wondering... is there a good way to manually set the access_token query parameter in the FB.api() request?

If the FB.init() cookie gets properly set, this is what the FB.api() request parameters look like:

access_token  xxxxxxxxxxxx|1.xxxxxxxxxxxxxxxxxxxxxx__.3600.xxxxxxxxxx-xxx|xxxxxxxxxxxxxxxxxxxxxxxxxxx
callback      FB.ApiServer._callbacks.xxxxxxxxxxxxxxx
pretty        0
sdk           joey

In Safari (or when the cookie FB.init() option is not set) the FB.api() request parameters look like:

callback      FB.ApiServer._callbacks.xxxxxxxxxxxxxxx
pretty        0
sdk           joey

Now... obviously my app has the ability to generate the access_token on the server side... I'm wondering if there's any way I can manually set FB.api() to use my server-side-generated access_token.

Clermontferrand answered 1/2, 2011 at 7:33 Comment(1)
I'm thinking there is another way to do this. Basically, since we have once chance to set cookies on the initial post request that populates the iFrame, we can use the opportunity to mimic the fbs_xxxxxxxxx cookie that the FB JS SDK tries to set during FB.init(). Now... the cookie value that the SDK sets looks like this: fbs_xxxxxxxxx="access_token=xxx&expires=xxx&secret=xxx&session_key=xxx&sig=xxx&uid=xxx". The access_token and expires parts are easy. But I'm having a little trouble figuring out the other parts.Clermontferrand
C
4

Ok I got it to work. I turns out you don't need the FB.init() cookie: true option in order to use FB.api()--you can manually pass the access_token:

FB.api("/me/apprequests/?access_token="+access_token, function (response) {});

Where access_token is a JS variable that you set server-side on page load. Here is an ERB example:

<script>
  var access_token = "<%= @access_token %>";
</script>

And @access_token is the oauth access_token that gets passed to your canvas page in the initial POST request.

Clermontferrand answered 1/2, 2011 at 19:27 Comment(0)
A
3

The limitation with Safari is that it will not allow the contents of an iframe to create or use cookies if the domain is different to the parent... unless the user interacts with it first. The problem is unique to Safari not webkit, so Chrome is fine.

After running into this problem myself (several painful times), there is a solution which appears to be a good work around. The technique is to inject a form into the page body using javascript and then trigger the submit event. Safari accepts this as a user interaction and allows cookies.

You can find a good example on the Facebook developer forum here

Artichoke answered 1/2, 2011 at 15:36 Comment(3)
Ah, not a bad idea. I know the JS-triggered form submit is how Facebook allows iFrame canvas pages to set cookies for the domain of the iFrame even on browsers like Safari that don't typically set cookies on x-domain iFrame requests (#4702422). However, it seems like triggering a form submit event from within the iFrame (or doing a JS reload) isn't the best user experience. Also, the Safari team might see that as a security hole that circumvents defense against ads that track users.Clermontferrand
It's possible to mask most of it from the user. I've used an implementation which tests for the user agent server side on the landing page, and then serves up html just to do the form trick for safari users. The transition is almost as fast as redirecting with a location header.Artichoke
Interesting. Well I'll definitely keep that in mind. For now I've realized that you don't actually need the FB JS SDK to set a cookie in order to use FB.api() -- so no need for browser-specific rules.Clermontferrand

© 2022 - 2024 — McMap. All rights reserved.