Shift8 Creative Graphic Design and Website Development

Facebook Connect & Lithium

Posted by Tom on Tue, Aug 31 2010 15:25:00

It seems to be a week full of different authentication methods here. Last I wrote about an LDAP Auth class adapter for Lithium that I wrote and now I'm going to go over using Facebook Connect with your Lithium project. As it turns out, it's also very easy and you can put everything you need within a library to keep things modular.

First things first, you'll need the Facebook Connect PHP SDK....It also wouldn't hurt to grab the JavaScript SDK. You'll probably end up using it. Continuing here with "step 1" you're going to alter that single PHP SDK file. I know, I know...Hang on and listen. When we use Lithium, we really like namespaces. I really pray for the day that all PHP libraries we pick up are namespaced, but that just isn't a reality yet. Fortunately the Facebook PHP SDK is very small. It's just one file with a few classes. Cool! So within that you'll put up at the very top: namespace facebook; Then you'll also need to put a slash (\) in three places. That's where you see (also up top) new Exception. Ensure those instantiations read new \Exception... Also where the FacebookApiException extends Exception, make that FacebookApiException extends \Exception. Alternatively, up top put: namespace \Exception; and you should also be ok. So you're really only talking about 2-4 small tiny itsy bitsy changes. So you can relax.

I'd put this into a library, right? Why not? So within your app\libraries folder make a "facebook" folder and rename that "facebook.php" file you just downloaded and make it "Facebook.php" because it's just not going to work otherwise. Ok? All good? At this point you have a "app\libraries\facebook\Facebook.php" file that has a few tiny tweaks to use namespaces. Cool? Ok, on with step 2.

Now let's setup our authentication in a pretty common fashion. We're talking about applying filters during the framework bootstrap process. Create a "config" folder and add a "bootstrap.php" file, so you have a "app\libraries\facebook\config\bootstrap.php" file. Within here is where you'll be applying some filters to setup the authentication for your site. Now, if you've done this before, it's going to be fairly familiar. I'm going to paste an example that will provide you Facebook Connect authorization only. It's different than what I'm using because I'm allowing people to login using accounts in my local database or with Facebook Connect. I also create a new user record upon logging in with Facebook Connect. That way users can create their local profiles just the same whether they registered with my site or used Facebook Connect bypassing a registration process. Your needs are most likely going to differ. If not, then maybe I can be convinced to wrap up a complete "User & Auth" library. Part of this is going to look exactly like the example they provide along with the FB PHP SDK. So, here we go:

use \lithium\storage\Session;
use \lithium\security\Auth;
use \lithium\action\Dispatcher;
use \lithium\action\Response;
use \facebook\Facebook;
use \facebook\FacebookApiException;

Session::config(array(
     'default' => array('adapter' => 'Php')
));

Dispatcher::applyFilter('run', function($self, $params, $chain) {
			
     // Create our Application instance (replace this with your appId and secret).
     $facebook = new Facebook(array(
          'appId'  => 'XXXXXXXXXX',
          'secret' => 'XXXXXXXXXXXXXXXXXX',
          'cookie' => true,
     ));
	
     $session = $facebook->getSession();
	
     $me = null;
     // Session based API call.
     if ($session) {
          // Write the session
          Session::write('fb_session', $session);
          try {
               $uid = $facebook->getUser();
               $me = $facebook->api('/me');
          } catch (FacebookApiException $e) {
               error_log($e);
          }
     }
	
     // login or logout url will be needed depending on current user state.
     if ($me) {
	  // This will come in handy later
	  Session::write('fb_logout_url', $facebook->getLogoutUrl());
	  	
	  // So set the Auth and pass along (in the session) the data from FB API
	  Auth::set('user', $me);
                
     } else {
	  // Again, this will come in handy (unless you're using the JavaScript SDK)
          Session::write('fb_login_url', $facebook->getLoginUrl());
	  
          // If no FB session, clear any local session we may have set
	  Auth::clear('user');
     }
        
     // Here's one way of locking out different actions based on login status.
     $blacklist = array(
          'pages/index',
	  'pages',
	  'recipes/create',
	  'dashboards/my_account'
     );

     $matches = in_array((string)$params['request']->url, $blacklist);
     if($matches && !Auth::check('user')) {
          return new Response(array('location' => '/users/login'));	 	
     }
     
     return $chain->next($self, $params, $chain);
});

That's pretty much all you need (in the least) in your bootstrap process. Step three; we're going to place a Login and Logout button on the site. So we need to go to the view template of your choice (could be a user's controller's "login" action or could be elsewhere). Let's say it's the login action. Again, I recommend using the JavaScript SDK as well. I'm also going to assume you'd be using jQuery. Here's what our template might look like:

<?php $html->script('http://connect.facebook.net/en_US/all.js', array('inline' => false)); ?>
<script>
// initialize the library with the API key
FB.init({
     appId   : 'XXXXXXXXXXXXXXXX',
     session : <?php echo json_encode(\lithium\storage\Session::read('fb_session')); ?>,
     status  : true,
     cookie  : true,
     xfbml   : true
});

// fetch the status on load
FB.getLoginStatus(handleSessionResponse);

$('#fb-connect-login').bind('click', function() {
     FB.login(handleSessionResponse, {perms:'read_stream,publish_stream'});
});

$('#logout').bind('click', function() {
     FB.logout(handleSessionResponse);
});

$('#disconnect').bind('click', function() {
     FB.api({ method: 'Auth.revokeAuthorization' }, function(response) {
         clearDisplay();
     });
});

// no user, clear display
function clearDisplay() {
     $('#user-info').hide('fast');
}

// handle a session response from any of the auth related calls
function handleSessionResponse(response) {
     // if we dont have a session, just hide the user info
     if (!response.session) {
          clearDisplay();
          return;
     }

     // if we have a session, query for the user's profile picture and name
     FB.api(
          {
          method: 'fql.query',
          query: 'SELECT first_name, last_name, pic_square FROM user WHERE uid=' + FB.getSession().uid
          }, function(response) {
                var user = response[0];
                $('#user-info').html('<img src="' + user.pic_square + '">' + user.first_name + ' ' + user.last_name).show('fast');
      });
}
</script>
<div>
     <button id="login"></button>
     <button id="logout"Logout></button>
     <button id="disconnect"></button>
</div>
<div id="user-info" style="display: none;"></div>

That's just about all you'll need. \lithium\storage\Session::read('fb_session'); is going to hold all your session data from Facebook, but you'll probably want to be using the JavaScript SDK to actually do anything useful with that information since it will be limited. Don't forget that facebook limits the information that you are allowed to store locally within your database. You of course could also continue to use the Facebook PHP SDK all over your site to get this information.

If you don't want to use the JavaScript SDK, you'll have the login URL set in the session data from the filters you applied. You'll grab it from: \lithium\storage\Session::read('fb_login_url');

Of course you can get more complex...You can setup a bunch of ACL based on your needs and make users perform other final registration tasks or you can, like I did, combine this with your User's model and allow people to register and login without Facebook Connect or login with Facebook Connect as an alternative.


[Back To Blog Index]