Facebook iPhone Session Proxy in PHP (FBSession getSessionProxy)

September 3rd, 2009

If your iPhone app incorporates the Facebook Connect for iPhone library, one of the first things you will do is instantiate an FBSession object. Developers are given two choices for this according to the wiki:

session = [FBSession sessionForApplication:myApiKey secret:myAppSecret delegate:self];

or:

session = [FBSession sessionForApplication:myApiKey getSessionProxy:myURL delegate:self];

The first option is quick to implement, and an OK option for initial testing. However, it is inherently insecure as it requires you to release your Facebook application’s secret key embedded in your code.

Unfortunately, implementing the second option isn’t straightforward. The wiki implies that you need to provide a proxy URL and that needs to be a thin wrapper for facebook.auth.getSession.

If you are using the standard PHP client libraries that Facebook provides, you may already be familiar with the auth_getSession function. auth_getSession abstracts things a bit and returns an associative array, while you need to provide the underlying XML. If you dig into the code a bit, you might notice that auth_getSession calls off to the call_method function, which in turn calls off to the post_request function. My initial thought (which others also had) was to try a solution like this:

$params = array('auth_token' => $_GET['auth_token'],
               'generate_session_secret' => TRUE);
$xml = $facebook->api_client->post_request('facebook.auth.getSession', $params);
header('Content-Type: text/xml;charset=utf-8');
echo $xml;

Unfortunately, this solution produces an “Invalid parameter” failure error from Facebook.

After digging further, it became apparent that the post_request function was adding an empty session_key parameter to the request. Facebook’s servers throw up an “Invalid parameter” failure error because it assumes if you are calling facebook.auth.getSession to get a session key, you shouldn’t be providing a session_key parameter (even if it is blank).

There’s no easy way around this with the client libraries, so I stripped the client library down to a thin wrapper that you can use as a session proxy. Just put this script on your server and point the getSessionProxy: parameter in your iPhone app to the URL where your script is hosted. Don’t forget to add in your Facebook app’s API key and secret token.

<?php
// Replace with your Facebook application's API Key and App Secret below
$appapikey = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
$appsecret = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';

// Adapted heavily from Facebook client libraries...

$post = array('method' => 'facebook.auth.getSession',
              'api_key' => $appapikey,
              'v' => '1.0',
              'auth_token' => $_GET['auth_token'],
              'generate_session_secret' => 1/*TRUE*/);

$post['sig'] = generate_sig($post, $appsecret);

$post_params = array();
foreach ($post as $key => &$val) {
  $post_params[] = $key.'='.urlencode($val);
}
$post_string = implode('&', $post_params);

header('Content-Type: text/xml;charset=utf-8');
echo post('http://api.facebook.com/restserver.php', $post_string);

function post($url, $post_string) {
  if (function_exists('curl_init')) {
    $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    $result = curl_exec($ch);
    curl_close($ch);
  }
  else {
    $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion();
    $context =
      array('http' =>
              array('method' => 'POST',
                    'user_agent' => $user_agent,
                    'header' => "Content-Type: application/x-www-form-urlencoded\r\n" .
                                'Content-Length: ' . strlen($post_string),
                    'content' => $post_string));
    $context_id = stream_context_create($context);
    $sock = fopen($url, 'r', false, $context_id);

    $result = '';
    if ($sock) {
      while (!feof($sock)) {
        $result .= fgets($sock, 4096);
      }
      fclose($sock);
    }
  }
  return $result;
}

function generate_sig($params_array, $secret) {
  $str = '';

  ksort($params_array);
  // Note: make sure that the signature parameter is not already included in
  //       $params_array.
  foreach ($params_array as $k=>$v) {
    $str .= "$k=$v";
  }
  $str .= $secret;

  return md5($str);
}

If you want a version of this in Python instead, check out this Facebook python authentication gateway post. I haven’t tested it, but it did provide some insight and inspiration for the PHP script above.

UPDATE: Fixed line 47 based on Nils’ comment. Thanks!

13 Responses to “Facebook iPhone Session Proxy in PHP (FBSession getSessionProxy)”

  1. cease Says:

    I see that you get auth_token as a request parameter. Where in the iphone code does this get set or generated. I tried createToken, but did not work either.

  2. aaron Says:

    cease-

    As far as I know, when you call:

    session = [FBSession sessionForApplication:myApiKey getSessionProxy:myURL delegate:self];

    the Facebook Connect code will automatically pass the auth_token for you. I assume this is randomly generated, or obtained from Facebook’s servers in a prior call, but would need to dig deeper.

  3. Nils Says:

    Hey there,

    many thanx for the script, saved a day :-)

    But I found a little bug you might not have found because curl is on your servers.

    Instead of
    Line 48: $sock = fopen($server_addr, ‘r’, false, $context_id);
    It should be:
    Line 48: $sock = fopen($url, ‘r’, false, $context_id);

    Cheers and as I said: Thanks!
    Nils

  4. Brent Goldman Says:

    I’m an engineer at Facebook on the Connect team. I want to correct a couple misconceptions with this post, as well as provide an alternative solution that allows you to work within the framework of the PHP client library instead of creating a new PHP Facebook wrapper.

    The reason the call to auth.getSession is failing has nothing to do with the empty session key. The
    problem relates to some magic that Facebook’s PHP client library does with the
    GET params and cookies during initialization. Specifically,if $_GET['auth_token'] is set, the Facebook PHP lib will call do_get_session(), which automagically calls auth.getSession on your behalf. This first call to auth.getSession (the automatic one) will “use up” the auth_token, invalidating it for future use. The second call to auth.getSession (the one you’re explicitly making) will then fail as a result. The “Invalid parameter” specifically refers to the invalid auth_token.

    I just wrote a new session proxy, made sure it worked on my test iPhone app, and then
    uploaded it to the wiki. Feel free to use this and suggest improvements.
    http://wiki.developers.facebook.com/index.php/Session_Proxy

  5. Mark Says:

    Brent,

    I’ve been trying your new session proxy, and it’s not working for me at all. I have my api_key and secret set correctly and I’m still getting Invalid parameter errors.

    Maybe you’ve done something with the setup of your application on the Facebook side that isn’t well documented. Do I need to set the callback URL? Should it be pointing to the URL of my proxy PHP script? What about the other URL? (mine is empty atm). Are there other settings that are required to make this work?

    Someone on the docs team there at FB should sit down with a new developer and watch them walk through the process using only the doc that is provided at http://wiki.developers.facebook.com/index.php/Facebook_Connect_for_iPhone

    I think you will see that there are problems with the doc. We are not psychic and we do not know what settings you have done that are not documented in order to get your app working.

    Fatal error: Uncaught exception ‘FacebookRestClientException’ with message ‘Invalid parameter’ in /fake-path-for-this-post/fb/facebook-platform/php/facebookapi_php5_restlib.php:3112
    Stack trace:
    #0 /fake-path-for-this-post/fb/facebook-platform/php/facebookapi_php5_restlib.php(309): FacebookRestClient->call_method(’facebook.auth.g…’, Array)
    #1 /fake-path-for-this-post/fb/auth-proxy.php(17): FacebookRestClient->auth_getSession(NULL, NULL)
    #2 {main}
    thrown in /fake-path-for-this-post/fb/facebook-platform/php/facebookapi_php5_restlib.php on line 3112

  6. Mark Says:

    OK, not only does Brent’s (official FB) example not work for me, the performant design one included above also gives me an Invalid parameter error. But at least in the latter case the error is in nice XML format! Cool! Now if I could figure out why I’m still getting the error…

  7. aaron Says:

    There are a lot of different settings that might be throwing things off… On the advanced settings tab for the app, you might want to look at a few things. Is sandbox mode on? If so, maybe turn it off. What do you have the application type set to? For this app in particular – for various reasons – I used web, but you may want to use desktop. If you use web, a canvas callback url is required on the canvas tab. If you use desktop, that’s not needed.

    Are you using the script above as-is (only changing the api key and session secret), or were other changes made?

  8. Mark Says:

    Thanks for replying.

    Here’s my theory for what my problem is. I don’t have the first clue about PHP, so I was running the PHP script from the command line with php scriptname.php, and expecting it to work. After digging into PHP a bit I see that $_GET is populated from the values set in an incoming http request, so it won’t contain anything if the script is run from the command line. Basic web 101 developer mistake type stuff.

    So for now I’m just hoping it’s working; I will only be able to verify it once I get it all coded up end-to-end in the app. Was hoping I could just try out the script but no such luck.

    Also tried it in a web browser with http://www.mydomain.com/scriptname.php?auth_token=&generate_session_secret=1 but that still gave an error:

    Fatal error: Uncaught exception ‘FacebookRestClientException’ with message ‘Invalid parameter’ in /somepath/fb/facebook-platform/php/facebookapi_php5_restlib.php:3112 Stack trace: #0 /somepath/fb/facebook-platform/php/facebookapi_php5_restlib.php(309): FacebookRestClient->call_method(’facebook.auth.g…’, Array) #1 /somepath/fb/auth-proxy.php(18): FacebookRestClient->auth_getSession(’e56974405663778…’, ‘1′) #2 {main} thrown in /somepath/fb/facebook-platform/php/facebookapi_php5_restlib.php on line 3112

    Maybe there are other parameters that are set under the hood when the call is made from the iPhone client library.

    Re: your questions, Sandbox mode is not on.

    As to the callback URL, I’m not sure why the choices are only web and desktop. I’m trying to add FBConnect to an iPhone app. Mine was set to web but I changed it to desktop now, and that hasn’t helped.

    Changes to the script: it’s as-is, except for my app’s api key and secret, and I changed the require line to point to the facebook.php file on my system, which resides in the untarred folder with the other FBConnect library files.

  9. Mark Says:

    Oops, in my URL in previous post after auth_token= it had a placeholder for my API key, but I wrote it (for the post) inside less-than/greater than, so it didn’t show up in my post.

    Trying again:
    http://www.mydomain.com/scriptname.php?auth_token={my_app_api_id_here}&generate_session_secret=1

  10. aaron Says:

    The auth_token is generated by Facebook and automatically passed by them… In short, you’ll more or less need a working iPhone app to test. Wish there were a simpler way, but can’t provide any pointers on that…

    In regards to the web vs. desktop setting, desktop should be the moral equivalent of an iPhone app – so unless you also have a web app to supplement the iPhone app, the desktop setting should be sufficient.

    Good luck! Hope this all helps once the app is ready.

  11. Mark Says:

    Update: after I set it up end-to-end, it is working. I didn’t have to do any further changes to the script, and I followed pretty much the instructions in the short iPhone FBConnect video. Moral of the story: FB isn’t really big on giving you ways to test individual components; you have to do big-bang development and then hope everything works at the end.

  12. Mark Says:

    The video I mentioned – pretty iPhone specific, and all iPhone developers will immediately find it, but in case someone is frustrated at reading the above and not having a link:

    http://www.vimeo.com/3616452

  13. Sid Ahuja Says:

    Thanks! This saved me a lot of time. You’re awesome to have posted this solution. God bless you

Leave a Reply

Powered by WP Hashcash