This article wills how you how to allow users to log in via facebook for your Rails application.

Rather than implementing yet another login system, many developers opt to utilize services such as facebook, twitter, or google instead. Not only is using these services more secure, but it also cuts down on the number of passwords users have to remember and it provides a more cohesive user experience.

Facebook App Setup

The first thing we need to do is set up a Facebook application. Simple visit http://developers.facebook.com/apps. You may need to set up a developer account if you don't already have one. Once you are there, Click the 'Create New App' button in the upper right. This will open up a dialog prompting you for some additional information:

Example Facebook Create App Dialog

Simply give your give your app a unique name and namespace, select a category and subcategory, and click Continue. You will then be presented with a captcha, fill it out and click continue.

Example Facebook Captcha Dialog

Next we will be presented with the basic app settings page. Note your application's id and secret. You will need these for later. Fill out as much of the basic info as you can, then click on 'Website with Facebook Login'. Once you do that it will ask you for a Site URL. For now, enter http://localhost:3000 and click Save Changes at the bottom. Later when you deploy your application to production you will need to go back to this settings page and set the Site URL to your actual URL. You can do this by clicking on your app after visiting the apps page and clicking 'Edit App' in the top right.

Facebook Basic Settings Example Screenshot

Prior to leaving this page it is recommended that you click on the App Details link on the left and fill out as many details as you can. Among other things it is important to fill out the Privacy Policy URL as well as the Terms of Service URL. If you don't have these right now that's okay, but please note that they are REQUIRED for production use. Once you are done, click save changes at the bottom.

Facebook App Details Example Screenshot

Finally, prior to pushing your application to production, you will need to disable sandbox mode. You can do this in the application settings page as mentioned earlier.

Building the Rails Application

Okay, now that we have the Facebook portion finished, we can build our Rails app. The first thing we will need to do is add the omniauth-facebook gem to our gemfile. Add the following line to your gemfile.

Gemfile:

gem 'omniauth-facebook', '~> 1.4.1'

Next, run a bundle install to install the new gem.

Terminal Commands:

bundle install

Now we will need to create an initializer to store our omniauth provider info. Create an initializer called omniauth and add in the code listed below.

config/initializers/omniauth.rb:

OmniAuth.config.logger = Rails.logger

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, 'my facebook app id', 'my facebook app secret', {:client_options => {:ssl => {:ca_file => Rails.root.join("cacert.pem").to_s}}}
end

Make sure to replace 'my facebook app id' with your app id, and 'my facebook app secret' with the values mentioned earlier. Note the last part of the code is specifying something called a ca_file. On many systems the SSL certificates can't be found which leads to an OpenSSL error. To resolve this, download this file and place it in your rails app root folder. This should resolve any OpenSSL issues that you are experiencing.

Now, we will need to create a User model to store our user information that Facebook returns. Run the commands below to create the user model.

Terminal Commands:

rails g model user provider uid name oauth_token oauth_expires_at:datetime
rake db:migrate

Great, now lets create a couple of controllers. The first controller, Home, is just a landing page. The second controller, Sessions, will allow the user to log in and out. Run the commands listed below to create these controllers.

Terminal Commands:

rails g controller home show
rails g controller Sessions create destroy

Now, lets add a few routes. Modify your routes file so it looks like the code listed below.

config/routes.rb:

FacebookAuthExample::Application.routes.draw do
    get 'auth/:provider/callback', to: 'sessions#create'
    get 'auth/failure', to: redirect('/')
    get 'signout', to: 'sessions#destroy', as: 'signout'

    resources :sessions, only: [:create, :destroy]
    resource :home, only: [:show]

    root to: "home#show"
end

The first route will tie the default omniauth provider callback to our sessions controller. The second route provides graceful error handling in case of a failure. The third route lets the user log out. The remaining 3 routes set up the sessions controller, home landing page, and a site root.

Now, lets add some code to our user model. Modify your User model so that it looks like the code listed below.

app/models/user.rb:

class User < ActiveRecord::Base
  def self.from_omniauth(auth)
    where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
      user.provider = auth.provider
      user.uid = auth.uid
      user.name = auth.info.name
      user.oauth_token = auth.credentials.token
      user.oauth_expires_at = Time.at(auth.credentials.expires_at)
      user.save!
    end
  end
end

This code will take the data returned by Facebook, try to find a corresponding user in our database, and then update or create the user as necessary.

Next, lets add some code to our application file to help us determine whether the user is logged in. Modify your application controller so that it looks like the code listed below.

app/controllers/application_controller.rb:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :current_user

  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
end

The helper_method tells rails we wish to use the current_user method as a helper method. The current_user method simply tries to look up the user by the user id stored in the seession.

Almost there, now lets add some code to our sessions controller to actually tie all of the pieces together. Modify your sessions controller so that it looks like the code listed below.

app/controllers/sessions_controller.rb:

class SessionsController < ApplicationController
  def create
    user = User.from_omniauth(env["omniauth.auth"])
    session[:user_id] = user.id
    redirect_to root_path
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_path
  end
end

Great! Finally, lets modify our application layout to include the login/logout links. Modify your application layout so that it looks like the code listed below.

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title>Facebook Auth Example</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>
<div>
  <% if current_user %>
    Signed in as <strong><%= current_user.name %></strong>!
    <%= link_to "Sign out", signout_path, id: "sign_out" %>
  <% else %>
    <%= link_to "Sign in with Facebook", "/auth/facebook", id: "sign_in" %>
  <% end %>
</div>
<%= yield %>

</body>
</html>

Great! Now if we start a rails development server and visit http://localhost:3000 we will see the sign in link at the top of the page. Clicking that link will show a prompt similar to the screen listed below. Once you click okay, you will be redirected back to your site and then logged in. If you want other people to be aable to sign in, you must either add them as a developer/tester in your application settings (click Developer Roles on the left) or disable sandbox mode.

Facebook Auth Example Screenshot

That's it! Thanks for reading!