Socialize with FBConnect

FBWhat?

According to Facebook, "Facebook Connect is a powerful set of APIs for developers that lets users bring their identity and connections everywhere." The idea is quite similar to OAuth or Yahoo's BBauth, but with a Facebook flavor.

You can read more technical stuff about it here:

http://wiki.developers.facebook.com/index.php/Getting_Started_with_Facebook_Connect

Why would you need it? The simple scenario: you can use it like a bizarro OpenID, until Facebook becomes an OpenID provider :) Some of you will just want this: "Allow people to authenticate on my website using their Facebook account."

But once the user is authenticated, your Catalyst app may also access the Facebook API using your user's credentials. Assuming she gives you permission, you can do all kinds of tricks, like getting the list of friends, avatar, and more.

I want it, what now?

Just use Catalyst::Authentication::Credential::FBConnect. Once authenticated, this will give you the Facebook user identifier and a session key you can later use with WWW::Facebook::API.

First of all, you need to signup as a developer and get an API Key, a Secret, and an Application Name. You'll need these to use Facebook Connect in Catalyst. Do your thing at http://developers.facebook.com and come back with the info.

Then you need to install Catalyst::Authentication::Credential::FBConnect from CPAN. It depends on WWW::Facebook::API, Moose, MooseX::Types::Moose and MooseX::Types::Common

Setting it up

First, set up your Catalyst application. You should be familiar with authentication realms by now; if not, take a look at Catalyst::Plugin::Authentication.

If you use the FBConnect credential to authenticate, you don't even need a database in your app. But most of the time you'll want to associate the Facebook user with a local user, and allow users to also authenticate the standard, password-based way. That's why we'll need two realms, facebook and dbic.

 package MyApp;

 __PACKAGE__->config( 'authentication' => {
     default_realm => 'facebook',
     realms => {
         facebook => {
             credential => {
                 class       => 'FBConnect',
                 api_key     => 'my_api_key',
                 secret      => 'my_secret',
                 app_name    => 'my_app_name', 
             }
         },
         dbic => {
             credential => {
                 class       => 'Password', 
                 password_type => 'none',
             },
             store => {
                 class       => 'DBIx::Class',
                 user_class  => 'DB::User',
                 id_field    => 'user_id'
             }
         }
     }
 } );

The user table should have some columns for holding the external credential info, if you want to associate the two. We're using credential_identifier to hold the Facebook uid. If you're using multiple external authentication systems (like OpenID or OAuth) it would be a good idea to specify the source for this particular credential in ( credential_source ).

 package MyApp::Schema::Result::User;
 use strict;
 use warnings;

 use base 'DBIx::Class';

 __PACKAGE__->table( 'users' );
 __PACKAGE__->add_columns(
     user_id => {
         data_type           => 'integer',
         is_auto_increment   => 1,
     },
     email => {
         data_type           => 'varchar',
         is_nullable         => 1
     },
     password => {
         data_type           => 'varchar',
         is_nullable         => 1
     },

     credential_identifier => {
         data_type           => 'varchar',
         is_nullable         => 1
     },

     credential_source => {
         data_type           => 'varchar',
         is_nullable         => 1
     },
 );

 __PACKAGE__->set_primary_key( 'user_id' );
 __PACKAGE__->add_unique_constraint( [ qw/ credential_identifier credential_source / ] );

 1;

The login action

The logic is simple: the first time you call $c->authenticate for the facebook realm, the user will be redirected to the Facebook login page. Once she manages to authenticate there, she will be sent back by Facebook to our application (in the same action), but accompanied by an auth_token. When authenticate is called this time, the user is authenticated and $c->user is created with the session information. All this logic is abstracted away inside the credential.

Once she's authenticated with FBConnect, she'll either register or login (hence find_or_create) in our internal user database. After that we'll just use the familiar API to reauthenticate the user in the dbic realm.

 sub login : Path('/login/facebook') {
     my ($self, $c) = @_;

     if( $c->authenticate( {}, 'facebook' ) ) {
         my $user = $c->model('DB::User')->find_or_create( {
             credential_identifier   => $c->user->session_uid,
             credential_source       => 'facebook',
         } );

         $c->authenticate( {
             credential_identifier   => $user->credential_identifier,
             credential_source       => 'facebook'
         }, 'dbic' ) or die "Login failed";
     }
 }

Connect an existing user

You can also assign a Facebook account to an already existing account:

 sub assign : Path('/assign/facebook') {
     my ($self, $c) = @_;

     my $user = $c->user if $c->user_in_realm('dbic');
     if( $c->authenticate( {}, 'facebook' ) ) {
         $user->update( {
             credential_identifier   => $c->user->session_uid,
             credential_source       => 'facebook',
         } );

         $c->authenticate( {
             credential_identifier   => $user->credential_identifier,
             credential_source       => 'facebook'
         }, 'dbic' ) or die "Login failed";
     }

 }

Use the Facebook API

All the work above gets you the uid from Facebook. This can be used later on with WWW::Facebook::API.

Here's some example code to actually use WWW::Facebook::API:

    my $client = WWW::Facebook::API->new(
        desktop         => 0,
        api_key         => 'my_api_key'
        secret          => 'my_secret'
    );

    #get user info
    my $response = $client->users->get_info(
        uids => $c->user->credential_identifier,
        fields => [ qw/about_me quotes/ ]
    );

    # get user's friends
    my $friends = $client->friends->get(
        uid => $c->user->credential_identifier
    );

More about this can be found in the WWW::Facebook::API docs, enjoy :)

AUTHOR

Cosmin Budrica <cosmin@sinapticode.com>