This gem provides authentication and basic Facebook functions for your Rails application.
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
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 IDapp_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 appapp_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 login
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
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).
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
.
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?
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