Using the omniauth-twitter gem with devise and rails 5

446 views Asked by At

I started watching Ryan Bates's tutorial on how to use Omniauth with devise (rails-cast 235 revised). I am having problems with the user.rb file. What he displays in his tutorial

devise :omniauthable, # ...

def self.from_omniauth(auth)
  where(auth.slice(:provider, :uid)).first_or_create do |user|
    user.provider = auth.provider
    user.uid = auth.uid
    user.username = auth.info.nickname
  end
end

def self.new_with_session(params, session)
  if session["devise.user_attributes"]
    new(session["devise.user_attributes"], without_protection: true) do |user|
      user.attributes = params
      user.valid?
    end
  else
    super
  end
end

def password_required?
  super && provider.blank?
end

def update_with_password(params, *options)
  if encrypted_password.blank?
    update_attributes(params, *options)
  else
    super
  end
end

isn't working for me, and it displays an ActiveModel::ForbiddenAttributesError with this line of code where(auth.slice(:provider, :uid)).first_or_create do |user| highlighted. I am thinking his version doesn't work because I am using Rails 5. Anyways I've tried modifying the user.rb file. With the following code.

 def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
   user.email = auth.info.email
    user.password = Devise.friendly_token[0,20]
    user.provider = auth.provider
    user.uid = auth.uid
    token = auth.credentials.token,
    secret = auth.credentials.secret
  end
end

def self.new_with_session(params, session)
    super.tap do |user|
      if data = session["devise.twitter_data"]
        # user.attributes = params
        user.update(
          email: params[:email],
          password: Devise.friendly_token[0,20],
          provider: data["provider"],
          uid: data["token"],
          token: data["credentials"]["token"],
          secret: data["credentials"]["secret"]
        )
      end
    end
  end

def password_required?
  super && provider.blank?
end

def update_with_password(params, *options)
  if encrypted_password.blank?
    update_attributes(params, *options)
  else
    super
  end
end
end

I am able to get the twitter auth page to show up load for about a minute and then redirect back to my user sign up page without signing in and also without displaying any error messages. By the way does anybody know how to sign in with facebook using devise.

1

There are 1 answers

0
Fabrizio Bertoglio On

The problem is related to the attribute auth that you are passing to self.from_omniauth(auth). You should set a binding.pry or puts auth on your server log so see how this variable looks like..

Probably when the self.from_amniauth(auth) method runs this auth.slice() it somehow run in error

Also the first_or_create method may be creating a user and you may be missing something, triggering an error. So try to with the debug show use the user.errors.full_messages

The idea hear is that the method is meant to find the User or create it. It is being called from the controller action Users::OmniauthCallbacksController#facebook

request means this is the outgoing request from your server to facebook or twitter..

Facebook or Twitter will reply to this request with an AJAX response, which will include the information you need to authenticate the user.

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?, you probably saved the env variable
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end, you probably saved the env variable
end

Then with the below method where(provider: auth.provider, uid: auth.uid).first_or_create you will either create the user or find it from your db. The problem is you are probably writing this method wrong or calling it with the wrong parameters, or you did not set up the api with twitter to correctly work...

from https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview

This action has a few aspects worth describing:

All information retrieved from Facebook by OmniAuth is available as a hash at request.env["omniauth.auth"]. Check the OmniAuth docs and each omniauth-facebook gem's README to know which information is being returned.

When a valid user is found, they can be signed in with one of two Devise methods: sign_in or sign_in_and_redirect. Passing :event => :authentication is optional. You should only do so if you wish to use Warden callbacks.

A flash message can also be set using one of Devise's default messages, but that is up to you.

In case the user is not persisted, we store the OmniAuth data in the session. Notice we store this data using "devise." as key namespace. This is useful because Devise removes all the data starting with "devise." from the session whenever a user signs in, so we get automatic session clean up. At the end, we redirect the user back to our registration form.

After the controller is defined, we need to implement the from_omniauth method in our model (e.g. app/models/user.rb):

def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    user.email = auth.info.email
    user.password = Devise.friendly_token[0,20]
    user.name = auth.info.name   # assuming the user model has a name
    user.image = auth.info.image # assuming the user model has an image
    # If you are using confirmable and the provider(s) you use validate emails, 
    # uncomment the line below to skip the confirmation emails.
    # user.skip_confirmation!
  end
end

This method tries to find an existing user by the provider and uid fields. If no user is found, a new one is created with a random password and some extra information. Note that the first_or_create method automatically sets the provider and uid fields when creating a new user. The first_or_create! method operates similarly, except that it will raise an Exception if the user record fails validation.