Posts tagged with omniauth

OmniAuth's Overzealous Approach to Facebook Auth

Call me a stickler, but I think there should be two pages that load quickest in any web app: The home page (for people not logged in) and the initial page you see once you are logged in, usually the dashboard.

The Good

Tackling the first of these two criteria, the guest home page, is fairly easy. This page could be as simple or as complex as you want. Facebook keeps it simple and static. Foursquare adds some flair. Whatever the approach may be, it's pretty easy to control the load time of the guest home page because you're likely building it from scratch.

OmniAuth had the revolutionary idea to consolidate third party methods of authentication, most using OAuth 1 or 2. But as consumers of libraries which take on such a burden, we have to be extra careful of the intricacies. For OmniAuth, one of those intricacies includes authentication with Facebook.

The Bad

When OmniAuth successfully authenticates with Facebook, somethings terrible happens: it makes a request to the Facebook Graph API...every...single...time. Not only the first time you log in with Facebook, but all subsequent times. This is because of the vast decoupling between OmniAuth and your app. OmniAuth knows nothing about your underlying data model so it can't reliably store the authenticated user's Facebook information (and know not to request it again). To provide the user's Facebook information within your success callback, OmniAuth makes a request to the Facebook API.

I think it goes without saying but this is really bad for usability. To glance at a couple problems with this approach, consider that the Graph API is down. Or, consider that it never responds at all. Or, consider that you're over your Facebook API quota. Your users will be sitting in limbo at the most critical time they're using your app, during the login process. Maybe this is their first time logging in. Making one API call could potentially make it their last. Impress users early and with OmniAuth's Facebook integration you could be missing out.

The Fix

The solution is to roll your own Authentication. Facebook's JavaScript SDK is awesome (most of the time) and you could probably integrate it within the same timeframe as you could OmniAuth but with the added benifit of a much better user experience. Unlike similar solutions to the Facebook JS SDK (Twitter @Anywhere), Facebook provides you with everything OmniAuth does, including the API access token.

Sidenote: As of this posting, OmniAuth 1.0 is currently under active development and it doesn't look like this issue has leaked into the OmniAuth Facebook Extension yet. The official release is still at 0.2.6.

Happy Facebooking!

Posted by Mike Pack on 09/21/2011 at 01:49PM

Tags: facebook, omniauth, api


Dynamically Requesting Facebook Permissions with OmniAuth

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!

Posted by Mike Pack on 04/27/2011 at 10:51AM

Tags: ruby, rails, omniauth, facebook