Search code examples
ruby-on-railsfacebooktwitteroauthomniauth

Getting (omniauth-facebook) and (omniauth-twitter) work


I'm using:

  • Ruby on Rails 4
  • devise 3.0.3
  • omniauth (1.1.4)
  • omniauth-facebook (1.4.1)
  • omniauth-twitter (1.0.0)

I recently set up my omniauth-facebook and everything works fine. Now i want to add omniauth-twitter but somehow i mess things up, pretty bad.

1.) To set up my Omniauth-Facebook i did this (in a nutshell):

gem 'omniauth'
gem 'omniauth-facebook'

2.) Added the columns "provider" and "uid" to my User model.

3.) Next, i declared the provider in my config/initializers/devise.rb:

require "omniauth-facebook"
config.omniauth :facebook, "App_ID", "App_Secret",
                                {:scope => 'email,offline_access',
                                 :client_options => {:ssl => {:ca_file => 'lib/assets/cacert.pem'}},
                                 :strategy_class => OmniAuth::Strategies::Facebook}

4.) I edited my Model User.rb

# Facebook Settings
def self.find_for_facebook_oauth(auth, signed_in_resource = nil)
    user = User.where(provider: auth.provider, uid: auth.uid).first
    if user.present?
        user
    else
        user = User.create(first_name:auth.extra.raw_info.first_name,
                                             last_name:auth.extra.raw_info.last_name,
                                             facebook_link:auth.extra.raw_info.link,
                                             user_name:auth.extra.raw_info.name,
                                             provider:auth.provider,
                                             uid:auth.uid,
                                             email:auth.info.email,
                                             password:Devise.friendly_token[0,20])
    end
end

and added the attributes to devise:

:omniauth_providers => [:facebook]

5.) I edited the routes:

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

THE END

Although this worked perfectly for Facebook, i tried for hours now to get this working for Twitter, and i just cant figure it out.

If someone, who has experience in this, or just knows the solution could help me set this up, i would be very thankful :)

Thank you guys, and sorry for the long Post.

In Addition

Twitter does not provide an :email Attribute so i have to Split up my User Registration Process i guess ?

My Twitter action in my User Model

# Twitter Settings
def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth[:provider], :uid => auth[:uid]).first
    unless user
        user = User.create(:first_name => auth[:name],
                                             :user_name => auth[:screen_name],
                                             :provider => auth[:provider], :uid => auth[:uid],
                                             :password => Devise.friendly_token[0,20]
        )
    end
    user
end

# build auth cookie hash for twitter
def self.build_twitter_auth_cookie_hash data
    {
        :provider => data.provider, :uid => data.uid.to_i,
        :access_token => data.credentials.token, :access_secret => data.credentials.secret,
        :first_name => data.name, :user_name => data.screen_name,

    }
end

I had to migrate a confirmable for Users -> How To: Add :confirmable to Users

My Form's Problem, (At Least im getting to this poing now :) )

enter image description here


Solution

  • To fix your problem with the email you could just set a dummy mail, or add a second step where the user adds his/her email.

    Dummy mail:

    class User < ActiveRecord::Base
    
      before_create :set_dummy_mail, if self.provider == "twitter"
    
      private
    
      def set_dummy_mail
        self.email = "#{self.name}_email@example.com"
      end
    
    end
    

    Or the second step option:

    Modify your controller to redirect to an add email step if the provider is twitter and the email is blank. Maybe you also have to modify your validations to allow email blank on create if the provider is twitter.

    UPDATE: I did it like following:

    Gemfile:

    gem "devise"
    gem "omniauth"
    gem "omniauth-facebook"
    gem "omniauth-twitter"
    

    I used:

    • devise version 2.2.3
    • omniauth 1.1.4
    • omniauth-facebook 1.3.0
    • omniauth-twitter 0.0.17

    If you are using different versions, you maybe must change a few things..

    routes.rb:

    devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
    
    devise_scope :user do
      post "account/create" => "users/accounts#create"
    end
    

    app/models/user.rb

    class User < ActiveRecord::Base
    
      # allow email blank for first create
      validates_format_of :email, :with => Devise.email_regexp, :allow_blank => true, :if => :email_changed?
    
      # facebook find method
      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(:first_name => auth.extra.raw_info.first_name, 
                             :last_name => auth.extra.raw_info.last_name, 
                             :facebook_link => auth.extra.raw_info.link, 
                             :user_name => auth.extra.raw_info.name
                             :provider => auth.provider, 
                             :uid => auth.uid, :email => auth.info.email, 
                             :password => Devise.friendly_token[0,20]
                            )
          user.confirm!
        end
        user
      end
    
      # twitter find method
      def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
        user = User.where(:provider => auth[:provider], :uid => auth[:uid]).first
        unless user
          user = User.create(:first_name => auth[:first_name], :user_name => auth[:user_name],
                             :provider => auth[:provider], :uid => auth[:uid], 
                             :password => Devise.friendly_token[0,20]
                            )
        end
        user
      end
    
      # build auth cookie hash for twitter
      def self.build_twitter_auth_cookie_hash data
        {
          :provider => data.provider, :uid => data.uid.to_i,
          :access_token => data.credentials.token, :access_secret => data.credentials.secret,
          :first_name => data.screen_name, :user_name => data.name,
    
        }
      end
    
    end
    

    app/controllers/users/omniauth_callbacks_controller.rb

    class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
    
      # callback action for facebook auth
      def facebook
        @user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
    
        if @user.persisted?
          sign_in_and_redirect @user, :event => :authentication
          set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
        else
          session["devise.facebook_data"] = request.env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    
      # callback action for twitter auth
      def twitter
        data = session["devise.omniauth_data"] = User.build_twitter_auth_cookie_hash(request.env["omniauth.auth"])
    
        user = User.find_for_twitter_oauth(data)
        if user.confirmed? # already registered, login automatically
          flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
          sign_in_and_redirect user, :event => :authentication
        elsif !user.email?
          flash[:error] = "You must add an email to complete your registration."
          @user = user
          render :add_email
        else
          flash[:notice] = "Please confirm your email first to continue."
          redirect_to new_user_confirmation_path
        end
      end
    
    end
    

    app/views/users/omniauth_callbacks/add_email.html.erb

    <%= form_for(@user, :as => "user", :url => account_create_path, :html => {:class => "form-inline"}) do |f| %>
      <%= f.email_field :email, :placeholder => User.human_attribute_name(:email), :class => "input-medium" %>
      <%= f.submit "Finish registration", :class => "btn btn-small" %>
    <% end %>
    

    app/controllers/users/accounts_controller.rb

    class Users::AccountsController < ApplicationController
    
      def create
        data = session["devise.omniauth_data"]
        data[:email] = params[:user][:email]
        user = User.find_for_twitter_oauth(data)
        user.email = data[:email]
    
        if user.save
          flash[:notice] = I18n.t "devise.registrations.signed_up_but_unconfirmed"
          redirect_to root_path
        else
          flash[:error] = I18n.t "devise.omniauth_callbacks.failure", :kind => data[:provider].titleize, :reason => user.errors.full_messages.first
          render "users/omniauth_callbacks/add_email"
        end
      end
    
    end
    

    Maybe you have to modify the one or other part of my solution..you also could refactor the two methods in the user model (find_for_facebook_auth, find_for_twitter_auth) to work with one dynamic method. Try it out and let me know, if you still have problems. If you find any typo, please also let me know.. Also you should write tests to check everything within your system.