Facebook OAuth using OmniAuth and Rails 4

Posted on Oct 21, 2013

Step 1: Installing gems

gem "omniauth"
gem 'omniauth-facebook', '1.4.0'
bundle install

Step 2

OmniAuth.config.logger = Rails.logger

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

Step 1: An app

Mine looked like this:

Click “Continue”, and you should be brought to an application screen that contains your App ID (or API Key) and your App Secret. Note those, but we’ll need to fill in some information here.

In the “App Domains” box, put ’localhost'

Click the green check on “Website with Facebook Login”, and fill that in with http://localhost:5000/. My students (or anyone who uses my Rails templates) will be able to use this. This value may be different for you– make sure that it matches whatever you use in development. localhost:3000 would be common if you are using rails s and appname.dev would be likely if you were using Pow.

Mine looked like this

Step 2: Installing application details

FACEBOOK_KEY=686338067928534
FACEBOOK_SECRET=60b9fe21ab431897eabc0743b39a5406

Step 1: Create a model to store the authentications

bundle exec rails g model Authentication user_id:integer provider:string uid:string name:string oauth_token:string oauth_expires_at:datetime
class CreateAuthentications < ActiveRecord::Migration
  def change
    create_table :authentications do |t|
      t.integer :user_id
      t.string :provider
      t.string :uid
      t.string :name
      t.string :oauth_token
      t.datetime :oauth_expires_at
 
      t.timestamps
    end
  end
end
class Authentication < ActiveRecord::Base

  belongs_to :user

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

end

Step 2: Create the authentication user interface

bundle exec rails g controller authentications index
class AuthenticationsController < ApplicationController

  def index
    @authentications = current_user.authentications if current_user
  end

  def create
    auth = Authentication.from_omniauth(current_user, env["omniauth.auth"])
    flash[:notice] = "Authentication successful."
    redirect_to authentications_url
  end

  def destroy
    @authentication = current_user.authentications.find(params[:id])
    @authentication.destroy
    flash[:notice] = "Successfully destroyed authentication."
    redirect_to authentications_url
  end

end
// Additional JS functions here
window.fbAsyncInit = function() {
  FB.init({
    appId      : 686338067928534, // App ID
    status     : true, // check login status
    cookie     : true, // enable cookies to allow the server to access the session
    xfbml      : true  // parse XFBML
  });
};

// Load the SDK Asynchronously
(function(d){
   var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
   if (d.getElementById(id)) {return;}
   js = d.createElement('script'); js.id = id; js.async = true;
   js.src = "//connect.facebook.net/en_US/all.js";
   ref.parentNode.insertBefore(js, ref);
}(document));

function fblogin() {
  FB.getLoginStatus(function(response) {
    if(response.status == "connected") {
      location.href =
        '/auth/facebook/callback?' +
        $.param({ signed_request: response.authResponse.signedRequest })
    } else {
     FB.login(function(response) {
      if (response.authResponse) {
          '/auth/facebook/callback?' +
          $.param({ signed_request: response.authResponse.signedRequest })
      }
     })
    }
  })
};
<% if @authentications %>
  <% unless @authentications.empty? %>
    <p><strong>You can sign in to this account using:</strong></p>
    <div class="authentications">
      <% for authentication in @authentications %>
        <div class="authentication">
          <%= image_tag "#{authentication.provider}_32.png", :size => "32x32" %>
          <div class="provider"><%= authentication.provider.titleize %></div>
          <div class="uid"><%= authentication.uid %></div>
          <%= link_to "X", authentication, :confirm => 'Are you sure you want to remove this authentication option?', :method => :delete, :class => "remove" %>
        </div>
      <% end %>
      <div class="clear"></div>
    </div>
  <% end %>
  <p><strong>Add another service to sign in with:</strong></p>
<% else %>
  <p><strong>Sign in through one of these services:</strong></p>
<% end %>

<!--
<a href="/auth/twitter" class="auth_provider">
  <%= image_tag "twitter_64.png", :size => "64x64", :alt => "Twitter" %>
  Twitter
</a>
-->
<% unless @authentications.select{ |a| a.provider == "facebook" }.any? %>
  <%= link_to_function image_tag("facebook_64.png", :size => "64x64", :alt => "Facebook"), 'fblogin()' %>
<% end %>
<!--
<a href="/auth/google_apps" class="auth_provider">
  <%= image_tag "google_64.png", :size => "64x64", :alt => "Google" %>
  Google
</a>
-->

<div class="clear"></div>
.authentications {
  margin-bottom: 30px;
}

.authentication {
  width: 130px;
  float: left;
  background-color: #EEE;
  border: solid 1px #999;
  padding: 5px 10px;
  -moz-border-radius: 8px;
  -webkit-border-radius: 8px;
  position: relative;
  margin-right: 10px;
}

.authentication .remove {
  text-decoration: none;
  position: absolute;
  top: 3px;
  right: 3px;
  color: #333;
  padding: 2px 4px;
  font-size: 10px;
}

.authentication .remove:hover {
  color: #CCC;
  background-color: #777;
  -moz-border-radius: 6px;
  -webkit-border-radius: 6px;
}

.authentication img {
  float: left;
  margin-right: 10px;
}

.authentication .provider {
  font-weight: bold;
}

.authentication .uid {
  color: #666;
  font-size: 11px;
}

.auth_provider img {
  display: block;
}

.auth_provider {
  float: left;
  text-decoration: none;
  margin-right: 20px;
  text-align: center;
  margin-bottom: 10px;
}

Step 3: Putting it all together

  resources :authentications

  match 'auth/:provider/callback', to: 'authentications#create', via: [:get, :post]
  match 'auth/failure', to: redirect('/'), via: [:get, :post]
//= require facebook
@import "authentications";
<%= link_to "Social Accounts", authentications_path %>
curl -o app/assets/images/facebook_32.png https://www.evernote.com/shard/s1/sh/c3303305-ff8f-4829-a315-9ee91709f479/49b3985624af8fb0cae8e3223333c61a/deep/0/facebook_32.png
cp app/assets/images/facebook_32.png app/assets/images/facebook_64.png

Step 4: Boot it up

bundle exec foreman start