Authentication with Facebook

This document provides an overview for how to set up authentication using Facebook accounts. Instead of maintaining login ids and passwords within the application, users authenticate their identity by logging into Facebook, using its login page and authentication process. This approach has the added benefit of providing secure means for submitting a password.

The document is written for Rails 3.0, but it should also work with Rails 3.1.

Facebook Registration

In order for your application to use Facebook's authentication process, you need to register your application. Go to https://developers.facebook.com/apps/. To register your application, you only need to provide the following information:

If you have deployed your application with an internet host name, you will want to change the Site URL to match the deployed URL.

Once you have registered your app with Facebook, note the App ID and the App Secret. You will need these strings when configuring your application.

Omniauth

Omniauth is a general authorization plugin for Ruby on Rails. To install it, add the following line to the file Gemfile:

gem "omniauth-facebook", :git => "git://github.com/mkdynamic/omniauth-facebook.git"

After adding this line to Gemfile, the command bundle install will add the omniauth gem and the needed customization for Facebook.

Next create a file called omniauth.rb and place it in the /config/initializers folder. The file should have the following contents:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, 'app-id', 'app-secret'
end

Make sure you substitute in your own App ID and App Secret for this file.

User model

A User model in the Rails app is useful is you want to track users and assign special privileges. In this example, we'll create a simple user model that keeps track of the Facebook id (uid), Facebook name (name) and whether the user has admin privileges:

This can be created with the following command:

  rails g scaffold User name:string uid:string admin:boolean

Additional attributes can be added for describing the user.

Don't forget the command rake db:migrate so that the table is actually created.

We will add login code so that a user object is automatically created when a new user authenticates with Facebook.

Creating the sessions controller

To allow a user to login and logout, a sessions controller can be created with the needed actions:

  rails g controller sessions new create destroy

While the generate commands creates routes for all three actions, the routes need to be edited in the routes.rb file (config folder) so that create uses the post method and destroy is categorized as a delete operation:

  match '/auth/:facebook/callback', :to => 'sessions#create'
  match '/login', :to => 'sessions#new', :as => 'login'
  match '/logout', :to => 'sessions#destroy', :as => 'logout'

Don't forget to remove the old routes! These new routes provide meaningful names for the URL that accesses them. For example, localhost:3000/login will be routed to the new action in the sessions controller. You can test whether the routes are correctly in place by running the command rake routes.

In the sessions controller, only the create and destroy actions require code:

  def new
    redirect_to '/auth/facebook'
  end
 
  def create
    auth = request.env["omniauth.auth"]
    info = auth["info"]
    name = info["name"]
    uid = auth["uid"]
    user = User.find_by_uid(uid)
    if not user
      user = User.create(:name => name, :uid => uid, :admin => false)
    end
    session[:user_id] = user.id
    session[:admin] = user.admin
    redirect_to root_path
  end

  def destroy
    reset_session
    redirect_to root_path, :notice => "Logged out"
  end

The new action just redirects the request to the path that Omniauth has set up for accessing the Facebook login page.

If successful, Facebook calls the create action. The request.env["omniauth.auth"] hash table has information on the user. This information is used to look up the User record (if it exists) or create a new User record if one doesn't exist yet.

The destroy action just sets the user_id in the session hash to nil before redirecting to the specified path.

To login, the user can click on a Login link created using the following code:

  <%= link_to 'Login', login_path %>

To logout, the user can click on a Logout link created using the following code:

  <%= link_to 'Logout', logout_path %>

Limiting Access

A controller can test whether a user is logged in by checking whether session[:user_id] is not nil. The value of session[:admin] indicates whether the user has admin privileges. Rails filters are typically used for checking login status and limiting access. These details are covered on pages 197 - 200 in the Agile Web Development book.

Troubleshooting