Thursday, June 28, 2012

Rails 3 : Devise : Omniauth : Facebook authentication

Today most of the clients prefer to have option for authentication through social sites as facebook/ twitter. As working of one of the project I was also adding facebook authentication for client project. Now the first choice was ofcourse to see if Ryan added any video for facebook authentication.

And I was lucky to get #360 Facebook Authentication just added, when I was looking for it. But I also found that there were some issues with devise, as I was not able to login.

I mean the simple project authentication was working great as shown. But when devise comes in it was having some problems. So I searched for devise and omniauth. And I found that I ignored the devise was ominauthable. So now I followed the step suggested in devise : omniauth :overview
Made some changes in my project as given in the example.

GemFile
gem "omniauth-facebook"
And run bundle command.
In devise.rb at top
require "omniauth-facebook"
and just uncommented
config.omniauth :facebook, "APP_ID", "APP_SECRET"
and changed the app_id and secret as per my facebook app generated.
I faced OpenSSL error but also the solution is available in the same example. Just changed the above config line to
config.omniauth :facebook, "APP_ID", "APP_SECRET",
      {:scope => 'email, offline_access', :client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}} 
Ofcourse app is running on heroku. Then added
:omniauthable
to the devise settings in user.rb (devise model)
In routes.rb I changed the previous single line
devise :users
to

devise_for :users, 
           :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

added a migration file
class AddAuthFieldsToUser < ActiveRecord::Migration
  def change
    add_column :users, :provider, :string
    add_column :users, :uid, :string
    add_column :users, :oauth_token, :string
    add_column :users, :oauth_expires_at, :datetime
  end
end
run rake db:migrate to add the columns in users table and add same columns to attr_accessible in user.rb. After that created new folder in controllers as users where added omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def passthru
  render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
    # Or alternatively,
    # raise ActionController::RoutingError.new('Not Found')
  end

  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)

    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
      redirect_to stores_path, :event => :authentication, :current_user => @user
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
end
In user.rb just removed name as its not in my users table.
  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.oauth_token = auth.credentials.token
      user.oauth_expires_at = Time.at(auth.credentials.expires_at)
      user.save!
    end
  end
  
  def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(  provider:auth.provider,
                           uid:auth.uid,
                           email:auth.info.email,
                           password:Devise.friendly_token[0,20]
                           )
      user.ensure_authentication_token!
      # added extra to create authentication token for user
    end
    user
  end
The main steps are done. Just need to restart the server and way to go with facebook authentication for my project.
The given wiki is also contains the example for OpenID and Google. Check the link given at the start. Hope that will solve your problem, as it solved mine.

5 comments:

  1. This is a solid post...just a quick question...does this add the email address of the facebook person to your DB?

    Thanks

    ReplyDelete
  2. This is a solid post...just a quick question...does this add the email address of the facebook person to your DB?

    Thanks

    ReplyDelete
  3. Hi, I keep getting this error: Expected omniauth_callbacks_controller.rb to define Users::OmniauthCallbacksController

    ReplyDelete
    Replies
    1. Hi,

      hope you have added the users folder in app/controllers, so the path for controller should be app/controllers/users/omniauth_callbacks_controller.rb

      Delete