How to integrate Facebook Connect with a Rails app

Update: I have written up another post for using Devise and Omniauth, and you can find it here. Facebooker is no longer maintained.

Top of my to-do list was to integrate Facebook Connect with my Rails app, since 1) there are A LOT of people using Facebook and 2) having to register to post or edit could be an obstacle in getting more users to use my site.

I looked on the web for a while, and found a great example by Stuart Eccles at Made by Many. It’s an awesome tutorial, but it’s for restful_authentication. I don’t use it, so I had to modify it a little bit. Also, I added a step to ask a user to pick a username. So the following instruction is basically modification of Stuart’s.

Without further ado, let’s begin.

1. Setup Facebook Application page

1.1. Go to this page, and enter a name for your application. I named mine “Playgrounds_R_Us”.

1.2. Make a note of Application ID, API Key, and Secret. You need it for facebooker.yml later.

1.3. Next is Authentication section. Here what’s important is Post-Authroize and Post-Remove Callback URL. They refer to a web page a user will be taken to after logging into and logging out of Facebook account. While testing, I left it at “http://127.0.0.1:3000″.

1.4. I also used the same URL (http://127.0.0.1:3000) for Canvas Callback URL in Canvas section and Connect URL in Connect section.

1.5. Then you are pretty much set with configuration on Facebook side.

2. Get facebooker plugin and install it. From your rails app root directory,

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


2.1 Configure facebooker.yml. You only need to care about those above “tunnel”. Canvas_page_name could be left blank. Use the keys you noted when you created your Facebook application.

development:
  api_key: [Your KEY]
  secret_key: [Your KEY]
  canvas_page_name: playgroundrus
  callback_url: http://127.0.0.1:3000/
  pretty_errors: true
  set_asset_host_to_callback_url: false
  tunnel:
    public_host_username:
    public_host:
    public_port: 4007
    local_port: 3000
    server_alive_interval: 0
production:
  api_key: [Your KEY]
  secret_key: [Your KEY]
  canvas_page_name: playgroundrus
  callback_url: http://www.playgroundrus.com
  set_asset_host_to_callback_url: false
  tunnel:
    public_host_username:
    public_host:
    public_port: 4007
    local_port: 3000
    server_alive_interval: 0

2.2 Make sure you generate “xd_receiver” files. Without, it won’t work correctly. Also from your rails app root directory,

script/generate xd_receiver

2.3. After that you are all set with facebooker configuration.

3. Prep the database. Add two fields to store facebook id and hashed email so that later we can find a user by either one of the two fields.

3.1. Generate a migration file

ruby script/generate migration AddFbsToUser

3.2. Add fields

def self.up
  add_column :users, :fb_id, :integer
  add_column :users, :email_hash, :string
end
 
def self.down
  remove_column :users, :fb_id
  remove_column :users, :email_hash
end

3.3. Add those fields with rake call.

rake db:migrate

4. Add Rails functions

4.1. Add the following two lines right after <body> tag in app/views/layouts/application.html.erb.

&lt;%= fb_connect_javascript_tag %&gt;
&lt;%= init_fb_connect "XFBML"%&gt;

4.2. Make sure you have the following line between header tags in app/views/layouts/application.html.erb.

&lt;%= javascript_include_tag :defaults%&gt;

4.3. Also add the followings in the ApplicationController in app/controller/application_controller.rb. When user logs into Facebook account, :facebook_session will have all the information about the user. :facebook_session is utilized a lot.

before_filter :set_facebook_session
helper_method :facebook_session

4.3. Also in ApplicationController, where I see if a user is logged in or not, I had to put additional code to see if it’s a facebook user. It’s a facebook user, if facebook_session is successfully created.

def fetch_logged_in_user
  if facebook_session
    @current_user = User.find_by_fb_user(facebook_session.user)
  else
    return unless session[:user_id]
    @current_user = User.find_by_id(session[:user_id])
  end
end

4.4. Now you can put the following line anywhere in a view, and you will get “Facebook Connect button”.

&lt;%= fb_login_button('window.location = "/users/link_user_accounts";')%&gt;

This will put a nice Facebook Connect button like this,

It could have normal facebook login button options like :size and :background. Check here for the options.

When a user clicks on the button, it will bring up a popup window where user can log in. After the user is logged in, link_user_accounts action in user controller will either create a new user with Facebook credentials or recognize a user as existing user by either email hash code or facebook user id. It will be covered later.

And also make sure the following line is added to the routes in config/routes.rb.

map.resources :users, :collection =&gt; {:link_user_accounts =&gt; :get}

4.5. In User model in app/models/user.rb, you can just use the same code as Stuart’s.

after_create :register_user_to_fb
 
#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_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(:username =&gt; "fb_#{fb_user.uid}", :password =&gt; "", :email =&gt; "")
  new_facebooker.fb_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_id)
  unless fb_id.nil?
    #check for existing account
    existing_fb_user = User.find_by_fb_id(fb_id)
 
    #unlink the existing account
    unless existing_fb_user.nil?
      existing_fb_user.fb_id = nil
      existing_fb_user.save(false)
    end
 
    #link the new one
    self.fb_id = fb_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 =&gt; email, :account_id =&gt; id}
  Facebooker::User.register([users])
  self.email_hash = Facebooker::User.hash_email(email)
  save(false)
end
 
def facebook_user?
  return !fb_id.nil? &amp;&amp; fb_id &gt; 0
end

But, mine had one difference. Since I didn’t use restful_authentication, my user model was different – no :name and :login fields. So, I changed mine to reflect my model.

new_facebooker = User.new(:username =&gt; "fb_#{fb_user.uid}", :password =&gt; "", :email =&gt; "")

Instead of

new_facebooker = User.new(:name =&gt; fb_user.name, :login =&gt; "facebooker_#{fb_user.uid}", :password =&gt; "", :email =&gt; "")

4.6. Add the followings in the User controller in app/controller/users_controller.rb.

def link_user_accounts
  if @current_user.nil?
    #register with fb
    User.create_from_fb_connect(facebook_session.user)
    user = User.find_by_fb_user(facebook_session.user)
    redirect_to edit_user_path(user)
  else
    #connect accounts
    @current_user.link_fb_connect(facebook_session.user.id) unless @current_user.fb_id == facebook_session.user.id
    redirect_to playgrounds_path
  end
end

Here, again mine is a little different, because I wanted to ask user to pick a username instead of temporary “fb_[numbers]” type of username. So, after a local user account is created, I direct the user to edit_user_path.

4.7. In Edit view of User controller, I see if it has a temporary username or not. If it does, I only ask user to pick a pretty username and perhaps location, which is required when registering in an old way.

&lt;% if @user.facebook_user? &amp;&amp; (@user.username.nil? || @user.username =~ /fb_\d/) %&gt;
  &lt;% form_for @user do |f| %&gt;
 
Please pick a pretty name for PlaygroundRUs: &lt;%= f.text_field :username %&gt;
 
Where do you live? <em>(Full address, city and state, or just zip)</em>: &lt;%= f.text_field :location %&gt;
 
&lt;%= submit_tag 'Update' %&gt;
 
  &lt;% end %&gt;
&lt;% else %&gt;
<h1>&lt;%= @user.username %&gt;</h1>
&lt;% form_for @user do |f| %&gt;
 
Email:
    &lt;%= f.text_field :email, :value =&gt; @user.email %&gt;
 
    &lt;% if !@user.facebook_user? %&gt;
 
Password:
      &lt;%= f.password_field :password %&gt;
 
Password Confirmation:
      &lt;%= f.password_field :password_confirmation %&gt;
 
    &lt;% end %&gt;
 
Location:
    &lt;%= f.text_field :location, :value =&gt; @user.location %&gt;
 
    &lt;%= submit_tag 'Update'%&gt;
 
  &lt;% end %&gt;
&lt;% end %&gt;

4.8. And in Update action of User controller, I make sure the user didn’t pick a duplicate username or email address.

def update
  @user = User.find(params[:id])
  if @user.facebook_user?
    if User.find_by_username(params[:user][:username]) || User.find_by_email(params[:user][:email])
      flash[:error] = "Username or email already exists!"
      redirect_to(edit_user_path) and return
    else
      @user.update_attribute(:username, params[:user][:username]) if params[:user][:username]
      @user.update_attribute(:location, params[:user][:location]) if params[:user][:location]
      @user.update_attribute(:email, params[:user][:email]) if params[:user][:email]
    end
  else
    @user.update_attributes(params[:user])
  end
  redirect_to user_path
end

4.9. For logging out, you can put this anywhere you like. Usually, I put it next to or below facebook icon. The following is in my application view in app/views/layouts/application.html.erb

&lt;% if @current_user.facebook_user? %&gt;
 
&lt;%= link_to 'Edit', user_path(@current_user) %&gt; | <a onclick="FB.Connect.logoutAndRedirect(&quot;/session/destroy&quot;)" href="#">Logout</a>
 
&lt;% end %&gt;

4.10. Last, but not the least, I found that even if a user is logged out from facebook, its cookie causes the site to think the user is still logged in, and refresh will bring the user back in (from the dead). To kill the ghost, make sure you have the following lines in the Destroy action in Session controller in app/controller/sessions_controller.rb.

def destroy
  if @current_user.facebook_user?
    clear_fb_cookies!
    clear_facebook_session_information
  end
  session[:user_id] = nil
  @current_user = false
  redirect_to playgrounds_path
end

5. When it’s time to deploy, make sure you change the test Callback URL (in my case, http://127.0.0.1:3000) to your production URL (in my case, http://www.playgroundrus.com).

Post to Twitter

35 thoughts on “How to integrate Facebook Connect with a Rails app

  1. Hey Yang …I saw your link via the Made By Many blog and I was following your instructions however I don’t see the actual facebook connect login button on my login page:

    I see the text “or login with Facebook connect” but no button.

    I think the facebooker plugin is installed correctly and I think this is something with local development and facebook markup language.

    My layout page has this in the header:

    And the login page there is this code:

    But no actual button…any thoughts why this may be? Thanks for a great post!

  2. I figured it out..there is something in the javascript that is faulty (mine of the plugin)

    I basically take this:

    which for me gets a javascript error:

    Error: Element.observe is not a function
    Source File: http://127.0.0.1:3000/login/login
    Line: 19

    since this gets an error doesn’t load the Facebook connect javascript

    Just hand write out the code:

    FB_RequireFeatures(["XFBML"], function() {
    FB.Facebook.init(’5e60cc30e56d1893e297680e85ce9e58′,’/xd_receiver.html’);

    });

    and it works for me (at least the button is showing)

    • Hey Law,

      Thanks. As long as you have

      1) < %= fb_connect_javascript_tag %> and
      < %= init_fb_connect "XFBML"%> in app/views/layouts/application.html.erb
      after body tag and before < %= yield %>, and

      2) “before_filter :set_facebook_session” and “helper_method :facebook_session” in ApplicationController,

      you should at least see the button. Perhaps you can double check?

      Also you probably want to make sure you have the following line in the header section in the app/views/layouts/application.html.erb.

      < %= javascript_include_tag :defaults%>

  3. Thanks for the tips on clearing the FB cookies, that was giving me some issues and I was looking for the right things to call. Thanks again.

  4. Hi I am working locally so i didnt specify the name of the site

    I follow all the steps specified above … but I cant see facebook button on the screen … I cant even see the sentence “login with facebook”.

    Is it the browser specific problem .. so that it cant read out the fbml or anyother prob …

  5. sth missing in application_controller : add “:fetch_logged_in_user”
    to be
    before_filter :set_facebook_session, :fetch_logged_in_user

    and i think destroy actions is a post action. so we cant make a get action on it. I recommend to use another action name instead.

    Thanks

  6. in the logout link… make it link to the right control destination sessions NOT session…

    <fb:profile-pic uid="” facebook-logo=”true” size=”thumb” >
    | Logout

    I made “end_session” action instead of “destroy”…
    in the routes.rb
    map.resources :sessions, :collection => { :end_session => :get }

    Regards!

  7. Hi! I updated my logout code with yours but my facebook session is still valid. I’m a ror newb and don’t know where to start looking for errors. Looking for a pointer in the right directon.

  8. I followed this tutorial to facebook connect rails application. Its a good tutorial. I want know that why fb_id, email_hash is stored. Is it possible to built without storing fb_id, email_hash.

  9. Great tutorial.

    I can login just fine but the problem is that my logout features doesn’t work because @current_user is always nil.

    What gives?

  10. Pingback: buy a castle

  11. Pingback: facebook secret

  12. Pingback: Cracked pc games

  13. Pingback: Chase Hosting

  14. Pingback: Facebook Connect with Rails (using Omniauth and Devise) [Update] « Yangtheman

  15. Simply want to say your article is as surprising.
    The clearness in your post is simply spectacular and i could assume you are an expert
    on this subject. Fine with your permission let me to grab your RSS feed to keep up to date with
    forthcoming post. Thanks a million and please carry on the rewarding work.

  16. I like the helpful information you provide for your articles.
    I’ll bookmark your blog and check once more right here regularly. I am relatively certain I will be informed a lot of new stuff right right here! Good luck for the next!

  17. Thanks so a immense deal pertaining to charitable me an inform lying
    on this matter on your web-site. Please realize that if a completely new post
    becomes available or when any improvements occur with the
    recent article, I would consider reading more and finding out how to make first-class using of those methods you discuss.
    Thanks for your efforts and consideration of other individuals by creation this blog available.

  18. I am curious to find out what blog platform you’re utilizing? I’m experiencing some small security problems with my latest site and I would like to find something more secure.
    Do you have any suggestions?

  19. Howdy, i read your blog from time to time and i own a similar one and i was just
    wondering if you get a lot of spam comments? If so how do you stop it, any
    plugin or anything you can recommend? I get so much lately it’s driving me insane so any assistance is very much appreciated.

    • Hi Ryan, I use Akismet WP plugin (since I am using WP), and it works quite well. I also moderate comments so that I don’t let any spammer leave a comment. I need to update this blog and reply to more comments….

  20. Howdy! This article couldn’t be written any better! Looking through this article reminds me of my previous roommate! He continually kept talking about this. I most certainly will send this article to him. Fairly certain he’ll have a great read.

    Thanks for sharing!

  21. Hello! Someone in my Facebook group shared this site with us so I
    came to check it out. I’m definitely enjoying the information. I’m book-marking and will be
    tweeting this to my followers! Great blog and amazing design and style.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>