Sliced Software

About

Thomas Mango writes software, has a bulldog, listens to Long Island Hardcore music and eats sandwiches.

He is currently working on Firefly, The Check-in Service for Twitter and Limited Pressing, An Online Store Community.

Making Your Existing User Base Play Nice With Your Facebook User Base

This is the second article in a series dedicated to getting your existing rails application on Facebook without clawing your eyes out. You should read the first article if you haven’t already.

In the previous article, I talked about how to use facebooker to extend your existing rails application with Facebook only views. Requests that came into your application from Facebook, were authenticated by facebooker to be genuine Facebook requests, the Facebook user was required to add your application and all of your Facebook specific .fbml.erb views and layouts were automatically used instead of your standard .html.erb views. While all of this was happening for requests coming from Facebook, your regular application kept chugging along, doing its own thing, unaware you were cheating on it with that blue haired girl from your Calc class.

Chances are however, your application has users (at least you hope it does). What you really want, is for users on your existing Rails app and new users coming in through Facebook to be treated like equal citizens. Sure you may email one and you may notify another, but aside from how you communicate with them, the content they generate in your application should be shared by everyone. If data generated by regular users and Facebook users couldn’t be seen or accessed by one another, there would be no reason to having a single action with two different views (html, fbml).

There are a few things we need to get our different types of users to play nicely.

  • A place to store a user’s Facebook user id and last session key.
  • A way to catch authenticated Facebook sessions for new users to our application.
  • A way to automatically log in an existing user who has authenticated via a Facebook request.

Tracking Facebook account details in your users table.

If you only plan on doing Facebook related stuff (you have no interest in having Twitter oauth support in your app in the future), the easiest thing to do is just add a couple of fields to your User model. We can just pull these migration lines from what facebooker does:

add_column :users, :facebook_id, :integer, :limit => 20, :null => false 
add_column :users, :session_key, string

Tracking Facebook account details in a separate object.

Alternatively, if you plan on having not only Facebook authentication, but Twitter oauth at some point, you may want to create a new model called FacebookAccount to store your user’s Facebook account details (as well as a foreign key back to the User it belongs to). Then you can add a TwitterAccount object as well and do what you have to do.

For this example, I’ll assume you just added some fields to your User.

Hey, do I know you?

Now that we have a place to store Facebook account details, we need a way to make a new User for a new Facebook user. Let me preface this by saying that there are probably a hundred different ways to do this. What I’m going to detail, however, is the approach that worked well for me.

Let’s go back to our application_controller and see how we left it:

before_filter :require_login_for_facebook

def require_login_for_facebook
  if params[:format] == 'fbml'
    ensure_authenticated_to_facebook

    if !session[:facebook_session].nil?
      # we'll do something with users
      # here in the next post about fb
    end
  end
end

If you follow a Facebook request coming into our application, you’ll see that it hits the require_login_for_facebook before filter, it should pass the (params[:format] == 'fbml') test because facebooker will have forced the format to be fbml because it already verified the request came from Facebook. Next, the famous ensure_authenticated_to_facebook method is called to make sure we get a Facebooker::Session. After that, I do one more test to ensure we really do have a Facebooker::Session sitting in session[:facebook_session] and we can be on our way.

Inside of the if statement checking if !session[:facebook_session].nil?, we’re going to want something along the lines of:

unless user_logged_in? or (controller_name == 'facebook' and action_name == 'connect')
  facebook_session = session[:facebook_session]
  if user = User.find(:first, :conditions => {:facebook_id => facebook_session.user.uid})
    session[:user_id] = user.id
  else
    redirect_to :controller => 'facebook', :action => 'connect'
  end
end

I should probably mention that I don’t use restful-authentication. I generally roll my own. Please, take a minute to yell and scream about what a horrible person I am. Blah blah blah. Okay, done? Despite not using the norm, you can see some of the same basic things here. First up, I have a helper method called user_logged_in?. Can you guess what that does? Good. I’m sure you have the same kind of method. Just make sure there isn’t already someone logged in. Don’t worry about that next part of the unless statement ensuring we aren’t in FacebookController#connect, you’ll understand that soon.

So, after we ensure we have a Facebooker::Session, we make sure there isn’t someone already logged in. Next, we try and locate the User in our users table that has a facebook_id that matches the user id of the Facebook authenticated user (session[:facebook_session].user.uid). If we find someone with that facebook_id, we can automatically log them in. For me, I set session[:user_id], for everyone else in the world, do what you normally do to log someone in.

The reason we can automatically log this person in is because that user already had to log into Facebook and when Facebook made a request to us, facebooker did the heavy lifting to ensure that this was a real Facebook request. We wouldn’t have gotten this far if it wasn’t a real Facebook request. And, since this is a real Facebook request, we can assume the uid of the user inside of the Facebooker::Session is really who they say they are. Thus, they are sufficiently authenticated.

But how do we actually get a user object with a facebook_id into the database so that we actually have someone to automatically log in? It’s actually pretty easy. First, generate a controller called FacebookController. Inside of this controller, create a method called connect. I know, I know, Facebook Connect! Although I won’t be talking about it in this article, this is actually in preparation for allowing users to add your Facebook app inside of Facebook, then go out to your existing external application and log in using Facebook Connect while still retaining the same user object they used from your Facebook applicaiton.

Inside of the FacebookController#connect method, you’re going to want to prepare a new User model. This method is going to act like a Users#new and Users#create method. The idea is that we need this user to essentially register for our site. User’s in our existing rails app register, so why not Facebook users? The difference, however, is that we want to ask for as little information as possible while still being able to have full fledged accounts for Facebook users that can be shown on our existing site. For me, that means I need a username. Just pick a username and I’ll be happy. Oh, and don’t forget to add an app/views/facebook/connect.fbml.erb view for this method (for Facebook Connect, we’d have a connect.html.erb - get it?).

def connect
	if request.get?
		# Lets collect some information about this user
		@facebook = Hash.new
		@facebook[:id] = facebook_session.user.uid
		@facebook[:name] = facebook_session.user.name
		@facebook[:description] = facebook_session.user.about_me
		@facebook[:image_small] = facebook_session.user.pic_square_with_logo
		@facebook[:image_large] = facebook_session.user.pic_big
		@facebook[:profile_url] = facebook_session.user.profile_url.gsub(/^http:\/\/www\.facebook\.com\//, '')

		# We're going to want this later, no point in hitting the API again
		session[:facebook_credentials] = @facebook

		# To make things easier, I like to suggest a username.
		# I won't do it here, but generally I run the suggested username
		# through a unique_username? check about 3 times, appending a 
		# random integer on the end to try and make it unique.
		# If I can't find a unique username quickly, I let the user worry about it.

		@user = User.new
		if !@facebook[:profile_url].blank? and !@facebook[:profile_url][/^profile.php/]
			# This user has a new vanity url so that is a preferred username
			@user.username = @facebook[:profile_url].first(15).gsub(/\s/, '').gsub(/\./, '')
		elsif !@facebook[:name].blank?
			# This user doesn't have a vanity url, just use their name
			@user.username = @facebook[:name].first(15).gsub(/\s/, '').gsub(/\./, '')
		end
	else
		@facebook = session[:facebook_credentials]

		# Create the new User object from params[:user]
		# Make sure to store the @facebook[:id] and session key (see facebooker docs)
		# Fetch and store their avatar locally (Facebook likes you to use their (x)fbml
		# tags to display a user's current profile picture, you could do that instead.)

		# Log the user in
		session[:user_id] = @user.id

		redirect_to path_to_front_page
	end
end

I know it looks like there’s a lot going on in the connect method, but it’s pretty straight forward and the inline documentation should really explain everything. Obviously, you’ll want a form in your app/views/facebook/connect.fbml.erb view. You’ll need a username field and you’ll probably want to display the image in @facebook[:image_small] on the page somewhere.

Now, whenever an existing Facebook user comes back to your application, they will automatically be logged in with their full fledged account. They never have to worry about logging in or the fact that they “registered”. In fact, I tell my user’s they are just picking a username. That is technically all they’re doing. Having to “register” scares people off. Picking a username is easy.

I may write another article that explains how you can setup Facebook Connect to allow your existing Facebook users to log in directly at your main external rails app (outside of Facebook), using Facebok Connect. Doing this would actually allow you to get users to sign up using Facebook Connect directly from your main site as well. After the post connect callback from Facebook Connect, you’d just the same kind of the check in the application controller to see if you have the user with that facebook_id, either log them in automatically or send them off to your FacebookController#connect method (only it will be the .html.erb version this time) to finish the account sign up. It’s really very similar to what we do here.

Looking for really old posts? Until they are here at tumblr, see here!