facebook API authentication in symfony using sfGuard

Posted by Mark on 2007-09-28 in , ,

Updated: The controller code that called the facebook API to insert the users first and last name was not correct. This has been updated.

Leveraging facebooks API to bring you users is a useful technique. As of this date, I haven't seen any plugins to automatically handle logging into an existing symfony application using facebook, so I did some integration work to make it possible. I may roll this up into a plugin at a later date, but time doesn't allow for that at the moment (if you want to do it yourself, then let me know and I'll give you any help I can)

Standard disclaimer applies - I've not yet significantly tested this code and it may eat your hamster.

Requirements

First, install sfGuardPlugin as described in their instructions. You'll also want to create a profile table with a couple of extra fields for facebook. If you're using an XML schema, your table declaration should look like this :

    <table name="sf_guard_user_profile" phpName="sfGuardUserProfile">
      <column name="id" type="integer" autoIncrement="true" primaryKey="true" />
      <column name="user_id" type="integer" required="true" />
      <foreign-key foreignTable="sf_guard_user">
        <reference local="user_id" foreign="id"/>
      </foreign-key>
      <column name="first_name" type="varchar" size="255" />
      <column name="last_name" type="varchar" size="255" />
      <column name="facebook_user_id" type="integer" />
      <column name="facebook_key" type="varchar" size="255" />
    </table>  
  

first_name and last_name are optional. If you decide not to use these, then you'll need to remove lines later on that populate these fields from the facebook API. You could also add other fields that are available from the facebook api for which the field list is available here.

Once sfGuard is installed and working (you should be able to log in as the admin user), you'll need to install the facebook PHP API client and place the PHP files included inside into your projects /lib directory. I also had to rename the facebook.php file Facebook.php otherwise symfonys classloader wouldn't recognise it. Your mileage may vary.

Extending sfGuard to use facebook

In order to extend sfGuard in your application, you need to create a sfGuardAuth module to override settings within the original sfGuardAuth application in the plugin. You can do this with the command line

    symfony init-module <your-application-name-here> sfGuardAuth
  

Once you've done this, remove the executeIndex method from the actions.class.php that is generated and the indexSuccess.php template. You'll need to copy some files from the plugin directly into the same place in your new module directory :

    plugins/sfGuardPlugin/modules/sfGuardAuth/config/security.yml
    plugins/sfGuardPlugin/modules/sfGuardAuth/templates/signinSuccess.php
  

Edit security.yml to add this at the end - this allows people who are not signed in to access the facebooksignin action

    facebooksignin:
      is_secure: off
  

You will also need to add both facebook_api_key and facebook_secret keys to your settings.yml containing your api key and secret.

Now add the following code into your new actions class :

    /**
     * executeFacebooksignin - log in using a facebook account
     *
     * @return void
     * @author Mark Ng
     **/
    public function executeFacebooksignin()
    {
      // force a facebook login
      $facebook = new Facebook(sfConfig::get('sf_facebook_api_key'),sfConfig::get('sf_facebook_secret'));
      $fbUserId = $facebook->require_login();

      $profile = sfGuardUserProfilePeer::retrieveByFacebookUserId($fbUserId);
      // check if a user profile exists with that facebook ID
      if ($profile instanceof sfGuardUserProfile)
      {
        $user = $profile->getSfGuardUser();
      }
      else
      {
        // no user with that facebook account, so make one
        $user = new sfGuardUser();
        $user->setUsername('facebook:'.$fbUserId);
        $user->save();
        $profile = $user->getProfile();
        $profile->setFacebookUserId($fbUserId);
      }
      // update these, as they could have changed and are not immutable
      $profile->setFacebookKey($facebook->api_client->session_key);
      $userinfo = $facebook->api_client->users_getInfo($facebook->user,array('first_name','last_name'));
      $profile->setFirstName($userinfo[0]['first_name']);
      $profile->setLastName($userinfo[0]['last_name']);
      $user->save();
      $profile->save();

      // sign the user in
      $this->getContext()->getUser()->signIn($user);

      $this->redirect('/');
    }
    
  

You may want to change the way that the username is assigned. If your application allows the : character in usernames, someone could possibly cause security mischief. You'll need to find some other way of assigning a facebook user a unique username in your system.

Then add a link to your signinsuccess.php template, to show a link to this action to log in to facebook something like this :

    <?php echo link_to('Sign in with your facebook account', 'sfGuardAuth/facebooksignin') ?>
  

And you should now be able to log in via facebook (after you've set your callback url in your facebook application).

I wrote this article directly after hacking this together for an application I was working on. If you find it doesn't work, it's likely that I've missed something. Please contact me via the contact section of the site and I'll help you fix it (and amend this page).

Leave a reply