FBAuth

This gem provides authentication and basic Facebook functions for your Rails application.

The Authentication Challenge

Facebook is an evolving platform, over the past couple years we've seen a lot of change in how it authenticates users of third-party applications.

The documentation for Facebook is minimal, without historic reference or in-depth discussion of the different implementation scenarios that our applications may take.

For example, the preferred method of doing "Canvas" applications, those that live within the Facebook "wrapper" is now to use an iFrame. If you choose to utilize the Javascript Connect SDK and rely on its authentication mechanisms this will fail under any browsers that block cross-domain cookies - which is more prominent as time goes on due to the security risks involved in allowing cross-domain cookies.

Therefore, as of this writing, applications using the prescribed iFrame style and using the JS SDK will fail on mobile Safari, Safari on Windows' default settings, and various mobile implementations of the Android browser.

This plugin uses a few techniques to locate your "access token" and prefers to use the OAuth API to get what you need as a Facebook app to ensure your users have correctly added your app, authenticated in Facebook, and to communicate with the Graph and Query APIs.

Here are the scenarios we currently handle:

iFrame Apps

  • first page load as an iFrame app inside Facebook, where authentication params are sent in the URL used for your iFrame

    • this is particularly required for mobile Safari and other browsers blocking cross-domain cookies by default
    • handles the old session parameter, as well as the new signed_request parameter
  • loading from the cookie initialized by the JavaScript API

    • works great for browsers supporting cross-domain cookies by default
  • the access token you get is time-limited, if it has expired you need to be re-authenticated

External (Connect) Apps

  • handling an OAuth exchange back & forth with Facebook to handle authentication and capture URL parameters back for token

Integration with Your Rails App

Add fbauth to your Gemfile

gem 'fbauth', '~> 1.0'

Create config/facebook.yml

development:
  app_id: 'xxxxxxxxxxxxx'
  app_context: 'my-app-dev'
  auth_path: '/login'
  canvas_url: 'http://dev.myapp.com/facebook'
  app_secret: 'xxxxxxxxxxxx'

test:
  app_id: 'fake_id'
  app_context: 'my-app'
  auth_path: '/login'
  canvas_url: 'http://myapp.com/facebook'
  app_secret: 'fake_secret'

production:
  app_id: 'xxxxxxxxxxxxx'
  app_context: 'my-app'
  auth_path: '/login'
  canvas_url: 'http://myapp.com/facebook'
  app_secret: 'xxxxxxxxxxxx'
  • app_id - this is your Facebook App ID
  • app_context - this is your Canvas Page path, ie. http://apps.facebook.com/my-app-dev
  • auth_path - the path in your application to your login page (must be a string, not a logical route name)
  • canvas_url - this is the Facebook Canvas URL, the base URL that gets to the Facebook iFrame pages for your Canvas app
  • app_secret - the Facebook App Secret code for your application

Note: We are assuming that you will be registering two facebook applications, one that you will be using to actively develop your application and the other for production use for your users.

Include the fbauth modules in your application controllers

include FacebookAuthFunctions
include FbauthHelper

In the controllers you want restricted to authorized users, or in your Application controller, add the filters

before_filter :require_facebook_auth

Create the controller and method that will live at your auth_path specified in your facebook.yml file.

class AuthController < ApplicationController

  # Use this if you've included FacebookAuthFunctions in your
  # Application controller...
  # skip_before_filter :require_facebook_auth

  def 
  end
end

And then create your login template (we use HAML, but you can use ERB if you want.

%div.fblogin
  You are not logged in to Facebook, please click here:
  %fb:login-button{:perms => 'publish_stream'} Log In to Facebook

%div.fbadd
  Please add this Application - it's great!
  %fb:login-button{:perms => 'publish_stream'} Add This Application

%div.fbready
  Please wait...

= fbauth_login_javascript(:login => '.fblogin', :add => '.fbadd', :ready => '.fbready')

The three areas noted above are exposed in the following scenarios:

  • :login - this element is exposed when the user has not logged into Facebook, they may or may not have added your application but we don't know that yet.
  • :add - this element is exposed when the user is logged in to Facebook but has not added / authorized your application. Note we are requesting certain permissions when the app is added: <fb:login-button perms="publish_stream"></fb:login-button>
  • :ready - once the user has satisfied the pop-ups that appear when logging in and/or adding the application, or if the user loads this screen and is already fully authenticated, this element appears and a redirect is done to your application root_path

Using the Facebook APIs

Graph API

The Graph API gives you the ability to request a great deal of information about individual social relationships and perform basic updates to that graph (ie. posting news-feed events).

Certain actions (posting changes, reading certain restricted attributes) are only available if you provide an access token and make an authenticated call.

Unauthenticated Call

graph = FacebookGraph.new
graph.call('svetzal')

This will retrieve publicly available information on the provided user's Facebook UID or handle in a Ruby hash structure.

{"name"=>"Steven Vetzal", "gender"=>"male", "id"=>"849395216", "last_name"=>"Vetzal",
 "locale"=>"en_US", "link"=>"http://www.facebook.com/svetzal", "first_name"=>"Steven"}

Authenticated Call

graph = FacebookGraph.new(fbauth.access_token)
graph.call('svetzal', { :scope => "birthday" })

This will make a similar call but as an authenticated caller we can request a restricted scope, in this case information about the user's birthday (if our app has requested the birthday permission from the user).

Query API (FQL)

Unauthenticated Call

query = FacebookQuery.new
query.fql('SELECT name, first_name, last_name FROM user WHERE uid in ("849395216","58001611","1018515154")')

FQL is handy for performing multiple lookups at once, and can save you a lot of latency.

[
  {"name"=>"Steven Vetzal", "last_name"=>"Vetzal", "first_name"=>"Steven"},
  {"name"=>"Nate Smith", "last_name"=>"Smith", "first_name"=>"Nate"},
  {"name"=>"Craig Savolainen", "last_name"=>"Savolainen", "first_name"=>"Craig"}
]

Authenticated Call

query = FacebookQuery.new(fbauth.access_token)

Manually Building an Access Token

If you scan your logs, you'll see the signed_request OAuth GET parameter being sent to your application. You can manually build yourself an authentication token from this using the following code.

data = FacebookDecoder.decode('{"signed_request"=>"oXWWpLi2tX7QW3cjX...(removed)"}')
fbauth = FacebookAuth.create(data)
fbauth.validate

The .validate step is optional, but pre-warns you if the token you are about to use is good. If it is not, you can find out why by looking at fbauth.validation_error.

Things Remaining Unclear

Documentation for the Facebook platform is a little fragmented, so we haven't (that we recall) come across the answers to these questions yet:

  • what timezone is the OAuth token expiry value in? (we get it in Epoch, no TZ data, currently assuming San Francisco)
  • what happens when time approaches the OAuth token expiry?
    • do we get a new one?
    • are we expected to stop functioning and redirect to a FB login?

Change Log

v1.2.0.4

  • Added support for Facebook iFrame POST behaviour
  • Fixed bug in Memcache client where we were using keys > 250 chars

v1.1.0.2

  • Added memcached caching of Facebook GET data, 60 seconds expiry
  • Fixed bug in Facebook JS SDK cookie interception for authentication

v1.0.0.2

  • Fixed bug where timing instrumentation reporting CPU time rather than wall time

v1.0.0.1

  • Added console application for quick testing, script/console

v1.0.0.0

  • Changed call semantics for FacebookGraph and FacebookQuery, use objects instead of class methods
  • Preparing for public release

v0.9.9.6

  • Raising info-rich exceptions when errors returned from Facebook on graph calls