Tutorial for restful_authentication on Rails with Facebook Connect in 15 minutes

[Update (10 April 2010): we've edited the tutorial to bring it up to date with the current incarnations of Facebook Connect, Facebooker and Rails.]

Back in June 2007 I wrote a popular tutorial on writing Facebook platform applications with Ruby On Rails. Time has moved on and Facebook has launched Facebook Connect which allows you to integrate Facebook into your own sites allowing authentication, registration, friend connecting, and Facebook feed posting in the context of your application. Mashable has a great post on 10 great implementations of Facebook Connect including Joost, Vimeo and Disqus.

At Made By Many we are fans of the possibilites of Facebook Connect for lowering barriers to registration, extracting social graph and injecting your social media functions into the daily online life of users. There is little point trying to create a “new” facebook on your site. Your unique social proposition lies elsewhere with your content, community and tools.

People have found the integration of Facebook Connect tricky and while great libraries like facebooker handle the API part, actually getting the profile linking and integration flow is harder. So I’ve written this tutorial to integrate the most commonly used starter plugin for authentication and registration in Ruby On Rails, restful_authentication, with Facebook Connect to allow your users to login and register through Connect.

First of all, let’s state what this integration is going to achieve:

  • As a user I can register to the site through entering my details so I can access all that great functionality
  • As a user I can login to the site through my entered username and password
  • As a user I can register to the site through Facebook Connect so I don’t have to fill in that form
  • As a user I can login to the site through Facebook Connect so I don’t have to remember two passwords
  • As a user I can connect my existing site user with my Facebook Connect user so I can later login through Facebook Connect

We also have a constraint we need to consider:

  • As a user if I register a user through entering my details and later login through Facebook Connect I want to make sure I retain my old user account

So read on and I’ll have you Connected in 15 minutes.

We will first create a standard restful_authentication Rails application. I’m going to user mysql for this example


rails -d mysql connect_tutorial
cd connect_tutorial

Then we need to install the restful authentication plugin:


cd vendor/plugins
git clone git://github.com/technoweenie/restful-authentication.git restful_authentication
cd ../..
./script/generate authenticated user sessions

Next, we create our database:


rake db:create
rake db:migrate

Now move include AuthenticatedSystem from the Sessions Controller to the Application Controller.

Start the server and browse to http://localhost:3000/signup. Bingo, Restful Authentication in 3 minutes. Don’t create any users yet we need to make to add some fields to connect up our accounts.

We need two extra columns for our users: one to store the Facebook user ID and another to store a special hash of our users email address which we can use to later match new Facebook users to existing accounts to take care of our constraint. Let’s create a migration for that:


script/generate migration add_users_fb

Edit the migration so that it looks like this:


def self.up
  add_column :users, :fb_user_id, :integer
  add_column :users, :email_hash, :string
  #if mysql
  execute("alter table users modify fb_user_id bigint")
end

def self.down
  remove_column :users, :fb_user_id
  remove_column :users, :email_hash
end

The ask Rake to run the migration:


rake db:migrate

For the Facebook heavy lifting, we are going to use the facebooker plugin. This will handle the API level communication for us.


script/plugin install git://github.com/mmangino/facebooker.git

You are now going to have to create a Facebook Application on Facebook to get your API key and secret. Head over to http://www.facebook.com/developers/createapp.php

picture-3

(Enter your own application name)

Facebook Connect setup screen

Take a note of the api_key and secret and add these to config/facebooker.yml. Also, make sure you set the callback_url to your local development server.


development:
  api_key: {YOUR_KEY}
  secret_key: {YOUR_SECRET}
  canvas_page_name:
  callback_url: http://localhost:3000/
  pretty_errors: true
  set_asset_host_to_callback_url: true
  tunnel:
    public_host_username:
    public_host:
    public_port: 4007
    local_port: 3000

Then, back at Facebook, select the Connect tab on the left of the page and enter your Connect URL (this used to be called the Callback URL) – for the purposes of this tutorial, you should set this to http://localhost:3000/ – make sure you include the trailing slash or Facebook’s form will complain:

fb-connect-screen-2

Now we need to create a cross-domain receiver file for Facebook Connect to callback on. Luckily, facebooker can do that for us, but make sure you have configured your facebooker.yml file correctly, or the generator will bomb out:


script/generate xd_receiver

We need to initialise the Facebook Connect on every page. This consists of 3 things:

  1. adding a namespace declaration for FBML
  2. adding the Facebook Connect Javascript
  3. initialising the Javascript

Luckily facebooker can do some of this for us, so we create a generic layout index.html.erb


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
<%= javascript_include_tag :defaults%>
</head>
<body>
<%= fb_connect_javascript_tag %>
<%= init_fb_connect "XFBML"%>
<%=yield%>
</body>

And add the following to ApplicationController


layout 'index'
before_filter :set_facebook_session
helper_method :facebook_session

You are now ready to roll with some Facebook Connect tags. Add the following to the bottom of sessions/new.html.erb


<p>or login with Facebook connect</p>
<%= fb_login_button('window.location = "/users/link_user_accounts";')%>

And the following to users/new.html.erb


<p>or register with Facebook connect</p>
<%= fb_login_button('window.location = "/users/link_user_accounts";')%>

We also add another registration field for name


<p><%= label_tag 'name' %><br/>
<%= f.text_field :name %></p>

These are going to create FBML tags which the Facebook connect Javascript will render as our Connect buttons

Start (or restart) the server and go to http://localhost:3000/login. The result should look like the following screenshot. If not, retrace your steps to make sure you’ve not done something wrong.

picture-5

Now it’s time to integrate. We need to do three main things

  1. When you are logged in through a Facebook session then login through restful authentication
  2. Link accounts between Facebook and Restful Authentication
  3. Create accounts when someone login or register with facebook.

In order to do this, we first need to edit lib/authenticated_system.rb. Change the current_user method to:


def current_user
  @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || login_from_fb) unless @current_user == false
end

Also, we need to add the following method:


def login_from_fb
  if facebook_session
    self.current_user = User.find_by_fb_user(facebook_session.user)
  end
end

This will handle the seamless login for us. Now we need to add to our User model as follows:


#find the user in the database, first by the facebook user id and if that fails through the email hash
def self.find_by_fb_user(fb_user)
  User.find_by_fb_user_id(fb_user.uid) || User.find_by_email_hash(fb_user.email_hashes)
end

#Take the data returned from facebook and create a new user from it.
#We don't get the email from Facebook and because a facebooker can only login through Connect we just generate a unique login name for them.
#If you were using username to display to people you might want to get them to select one after registering through Facebook Connect
def self.create_from_fb_connect(fb_user)
  new_facebooker = User.new(:name => fb_user.name, :login => "facebooker_#{fb_user.uid}", :password => "", :email => "")
  new_facebooker.fb_user_id = fb_user.uid.to_i
  #We need to save without validations
  new_facebooker.save(false)
  new_facebooker.register_user_to_fb
end

#We are going to connect this user object with a facebook id. But only ever one account.
def link_fb_connect(fb_user_id)
  unless fb_user_id.nil?
    #check for existing account
    existing_fb_user = User.find_by_fb_user_id(fb_user_id)
    #unlink the existing account
    unless existing_fb_user.nil?
      existing_fb_user.fb_user_id = nil
      existing_fb_user.save(false)
    end
    #link the new one
    self.fb_user_id = fb_user_id
    save(false)
  end
end

#The Facebook registers user method is going to send the users email hash and our account id to Facebook
#We need this so Facebook can find friends on our local application even if they have not connect through connect
#We hen use the email hash in the database to later identify a user from Facebook with a local user
def register_user_to_fb
  users = {:email => email, :account_id => id}
  Facebooker::User.register([users])
  self.email_hash = Facebooker::User.hash_email(email)
  save(false)
end
def facebook_user?
  return !fb_user_id.nil? && fb_user_id > 0
end

This allows authentication to look up users either from their stored Facebook ID, or a hash of their email address. It also adds methods for our creating and linking. After any user is created we need to register them we Facebook Connect so add to the User model


after_create :register_user_to_fb

In the previous view’s Facebook Connect login button we added an after login JavaScript callback. This is to link our accounts after a user has gone through the callback process. We need to add this to the user controller


def link_user_accounts
  if self.current_user.nil?
    #register with fb
    User.create_from_fb_connect(facebook_session.user)
  else
    #connect accounts
    self.current_user.link_fb_connect(facebook_session.user.id) unless self.current_user.fb_user_id == facebook_session.user.id
  end
  redirect_to '/'
end

Don’t forget to add a route for this, as follows (make sure you replace the existing route for :users)


map.resources :users, :collection => {:link_user_accounts => :get}

Finally we need to have somewhere to go after login. Let’s create a home page under Users controller users/home.html.erb


<% if logged_in? %>
<h2>You are logged in as <%= current_user.name %></h2>
<% if current_user.facebook_user? %>
<fb:profile-pic uid="<%= current_user.fb_user_id%>" facebook-logo="true" size="thumb" ></fb:profile-pic>
<p><a href="#" onclick='FB.Connect.logoutAndRedirect("/logout")'>Logout</a></p>
<% else %>
<p>why don't you connect with your facebook account</p>
<%= fb_login_button('window.location = "/users/link_user_accounts";')%>
<p><%= link_to 'Logout', logout_path%></p>
<% end %>
<% else %>
<h2>You are not logged in!</h2>
<p><%= link_to 'Signup', signup_path%> or <%= link_to 'Login', login_path%></p>
<% end %>

And map it to root and delete public/index.html


map.root :controller => "users", :action => "home"

And it’s done. Stop the clock. Start (or restart) the server and go to http://localhost:3000/login and press the big connect button

picture-9

Login with your Facebook account. Restful Authentication with Facebook Connect. Done!

picture-7

Hope this helps you guys. You can find more Facebook Connect documentation on the developer wiki. I’m hoping to add to this tutorial with posting to the feed and connecting with friends so follow me @stueccles on Twitter for updates. You can find the code for this tutorial at github.

About the author

Stuart is a technologist’s technologist and one of the founding partners at Made By Many. He also is a champion of fluid, Agile business structures and new disruptive business models for a disruptive age. Follow @stueccles on Twitter

Leave a comment

Our latest tweets

Categories

Recent comments

  • George Gerrard: magic to at last see/read someone with a brain I have been on chatroulette and as soon as I see some...
  • Oli Matthews: Thanks Rob. It's a little like tweet deck but with Flickr included. As mentioned in my post I'm goin...
  • Robz J: Nice work Ol -kinda like Tweetdeck right ? Interested also to hear your comments on browsers >>...
  • Raj: Check out the Tfl London Cycle Hire API @ http://bike-stats.co.uk/...
  • Andrew Denny: Oops, sorry, glitch in my last comment. God, I hate this commenting system, why can't they design a ...