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.
<%= fb_connect_javascript_tag %>
<%= init_fb_connect "XFBML"%>
4.2. Make sure you have the following line between header tags in app/views/layouts/application.html.erb.
<%= javascript_include_tag :defaults%>
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”.
<%= fb_login_button('window.location = "/users/link_user_accounts";')%>
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 => {:link_user_accounts => :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 => "fb_#{fb_user.uid}", :password => "", :email => "")
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 => email, :account_id => id}
Facebooker::User.register([users])
self.email_hash = Facebooker::User.hash_email(email)
save(false)
end
def facebook_user?
return !fb_id.nil? && fb_id > 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 => "fb_#{fb_user.uid}", :password => "", :email => "")
Instead of
new_facebooker = User.new(:name => fb_user.name, :login => "facebooker_#{fb_user.uid}", :password => "", :email => "")
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.
<% if @user.facebook_user? && (@user.username.nil? || @user.username =~ /fb_\d/) %>
<% form_for @user do |f| %>
Please pick a pretty name for PlaygroundRUs: <%= f.text_field :username %>
Where do you live? (Full address, city and state, or just zip): <%= f.text_field :location %>
<%= submit_tag 'Update' %>
<% end %>
<% else %>
<%= @user.username %>
<% form_for @user do |f| %>
Email:
<%= f.text_field :email, :value => @user.email %>
<% if !@user.facebook_user? %>
Password:
<%= f.password_field :password %>
Password Confirmation:
<%= f.password_field :password_confirmation %>
<% end %>
Location:
<%= f.text_field :location, :value => @user.location %>
<%= submit_tag 'Update'%>
<% end %>
<% end %>
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
<% if @current_user.facebook_user? %>
<%= link_to 'Edit', user_path(@current_user) %> | Logout
<% end %>
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).
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!
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)
and
in app/views/layouts/application.html.erb
after
tag and before
, and
2) ”
” and ”
” 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.
I actually figured it out. You need to configure facebooker.yml before calling a script to generate xd_receiver.html. I fixed my post as well.
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.
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 …
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
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!
Good post. One thing that I noticed on Stuart’s post and yours is that you cannot store the user’s name more than 24h – http://wiki.developers.facebook.com/index.php/Storable_Data
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.
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?
Thanks for the interesting post. I’m going to share with my friends and look forward to visiting this site again!
Thanks for visiting! But, Facebooker is really outdated. You should look into Omniauth plug-in!
https://github.com/intridea/omniauth
This is awesome. One of my clients works with rails and I’ll send this on to him.
Yang you really are the man! LOL – been pulling my hair out with an issue at work but reading this triggered a few things in my head to search for!
Great code! Have there been any updates to it since you posted it?
Pingback: buy a castle
Pingback: facebook secret
but facebook not giving APP KEy.So what to do ??
Pingback: Cracked pc games
Pingback: Chase Hosting
Pingback: Facebook Connect with Rails (using Omniauth and Devise) [Update] « Yangtheman
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.
Yangtheman!
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!
Hello colleagues, pleasant article and pleasant urging
commented here, I am truly enjoying by these.
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.
Excellent site you have here.. It’s difficult to find high-quality writing like yours nowadays. I really appreciate individuals like you! Take care!!
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?
This specific blog, “How to integrate Facebook
Connect with a Rails app | Yangtheman” was perfect.
I am printing out a replicate to clearly show my personal good friends.
Regards-Charmain
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….
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!
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.