2 minute read

One of the major benefits of dynamically requesting the Facebook permissions is the increased rate of users who will allow you to access their account. Facebook puts it nicely, “There is a strong inverse correlation between the number of permissions your app requests and the number of users that will allow those permissions.”

This solution uses OmniAuth to handle the authentication. The concept is simple. Ask the user to allow your application access to their most basic information (or the bare minimum your app needs). When they perform an action that requires more than the permissions they have currently allowed, redirect them to Facebook and ask for more permissions.

If you haven’t set up OmniAuth, follow Ryan Bate’s Railscasts, Part 1 and Part 2.

Configuring OmniAuth

OmniAuth expects you to configure your authentication schemes within your initializers.

config/initializers/omniauth.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV['FB_APP_ID'], ENV['FB_APP_SECRET']
end

Now, when you visit /auth/facebook, you will be redirected to Facebook and asked for basic permissions.

In order to permit your app to dynamically change the OmniAuth Stategy, you’ll need a controller which has access to your OmniAuth Strategy. OmniAuth provides a pre-authorization setup hook to handle this. Update your omniauth.rb initializer to look like the following:

config/initializers/omniauth.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV['FB_APP_ID'], ENV['FB_APP_SECRET'], :setup => true
end

Now when you visit /auth/facebook you’ll be redirected to /auth/facebook/setup. You should add a route for this:

config/routes.rb

match '/auth/facebook/setup', :to => 'facebook#setup'

Your Facebook controller with the setup action should look as follows:

app/controllers/facebook_controller.rb

class FacebookController < ApplicationController
  def setup
    request.env['omniauth.strategy'].options[:scope] = session[:fb_permissions]
    render :text => "Setup complete.", :status => 404
  end
end

Note: OmniAuth says, “we render a response with a 404 status to let OmniAuth know that it should continue on with the authentication flow.”

Once your FacebookController#setup action has completed, OmniAuth will take it from there and process your request through to Facebook.

Dynamically Setting the Permissions

The appropriate code can be used like so:

app/controllers/some_controller.rb

session[:fb_permissions] = 'user_events'
redirect_to '/auth/facebook'

session[:fb_permissions] is the interface between your two controller actions: the one that wants to request more permissions (some_controller.rb) and the one that wants to modify your OmniAuth Strategy (facebook_controller.rb).

For reference, here’s a list of available Facebook permissions you can use; comma deliminated.

That’s it. Upon redirection, Facebook will ask to allow the new permissions, redirect back to your app, and you can now successfully make calls to the Facebook API (I use Koala to work with the Facebook API).

One Gotcha

One thing I ran into on OmniAuth 0.2.4 and Rails 3.0.7 is the OmniAuth Stategy which was available in request.env['omniauth.stategy']. If you have more than one provider in your OmniAuth::Builder DSL, request.env['omniauth.stategy'] will be set to the last entry in the DSL. If you have your initializer set up like the following:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV['FB_APP_ID'], ENV['FB_APP_SECRET'], :setup => true
  provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET']
end

request.env['omniauth.stategy'] will be set to #<OmniAuth::Strategies::Twitter>, not exactly what you want. Your Facebook stategy needs to be the last entry in the DSL, like so:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET']
  provider :facebook, ENV['FB_APP_ID'], ENV['FB_APP_SECRET'], :setup => true
end

Happy Facebooking!

9 comments