Remon Amin
Remon Amin

Reputation: 1548

connect with facebook for existing users

I am using omniauth-facebook gem with devise to authenticate with Facebook in my rails application in my user model

def self.from_omniauth(auth)
  # immediately get 60 day auth token
  oauth = Koala::Facebook::OAuth.new("App Key",  "App secrets" )
  new_access_info = oauth.exchange_access_token_info auth.credentials.token
  new_access_token = new_access_info["access_token"]
  new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
  begin
    where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
      user.provider = auth.provider
      user.uid = auth.uid
      user.username = auth.info.first_name
      user.lastname =auth.info.last_name
      user.email =auth.info.email
      user.authentication_token = new_access_token 
      user.oauth_expires_at = new_access_expires_at 
      user.save!
    end
  rescue  ActiveRecord::RecordInvalid
  end
end

#interact with facebook
def facebook
  @facebook ||= Koala::Facebook::API.new(authentication_token)
  block_given? ? yield(@facebook) : @facebook
rescue Koala::Facebook::APIError => e
  logger.info e.to_s
  nil # or consider a custom null object
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

and on my omniauth_callbacks controller I have this method:

def all
  user = User.from_omniauth(request.env["omniauth.auth"])
  if user.persisted?
    flash.notice = "Signed in!"
    sign_in_and_redirect user
  else
    session["devise.user_attributes"] = user.attributes
    redirect_to new_user_registration_url
  end
end
alias_method :facebook, :all

These methods are used to authenticate user from scratch via Facebook. I need a way to connect existing users with their Facebook accounts not new ones if they registered via normal devise registration method

When an existing user is trying to sign in via Facebook the following error occurs:

A `NoMethodError` occurred in `omniauth_callbacks#facebook`:

undefined method `persisted?' for nil:NilClass
 app/controllers/omniauth_callbacks_controller.rb:4:in `all'

Upvotes: 1

Views: 1420

Answers (3)

svelandiag
svelandiag

Reputation: 4320

Just for add an updated version of the answer for rails 4 and devise 3.4.x

Like this is how an updated omniauth would look like

  def self.from_omniauth(auth)
    if !where(email: auth.info.email).empty?
      user = where(email: auth.info.email).first
      user.provider = auth.provider # and connect him to facebook here
      user.uid = auth.uid           # and here
      user.save!
      user
    else
      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.first_name = auth.info.name   # assuming the user model has a name
        user.avatar = process_uri(auth.info.image) # assuming the user model has an image
      end
    end
  end

Just look for user by email, if there is one, just add the provider and uid, if there is not one, just create it as suggested in the documentation

Upvotes: 1

Trantor Liu
Trantor Liu

Reputation: 9136

You can have your from_omniauth be something like this:

def self.from_omniauth(auth)
    where(auth.info.slice(:email)).first_or_create do |user|
      user.email = auth.info.email
      user.password = Devise.friendly_token[0,20]
      user.username = auth.info.name
      user.description = auth.extra.raw_info.bio
    end
end

In this way, if there's an existing user with email same as the facebook account, then the first_or_create will return it and then the user can sign in (it won't be update).

Upvotes: 0

ck3g
ck3g

Reputation: 5929

You can find user by email first and just update provider and uid fields in case he is already exists.

So your User.from_omniauth may looks like that:

def self.from_omniauth(auth)
  # immediately get 60 day auth token
  oauth = Koala::Facebook::OAuth.new("",  "" )
  new_access_info = oauth.exchange_access_token_info auth.credentials.token
  new_access_token = new_access_info["access_token"]
  new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds

  user = where(provider: auth.provider, uid: auth.uid).first
  unless user
    # in that case you will find existing user doesn't connected to facebook
    # or create new one by email
    user = where(email: auth.info.email).first_or_initialize
    user.provider = auth.provider # and connect him to facebook here
    user.uid = auth.uid           # and here
    user.username = auth.info.first_name
    user.lastname = auth.info.last_name
    user.authentication_token = new_access_token
    user.oauth_expires_at = new_access_expires_at
    # other user's data you want to update
    user.save!
  end

  user
end

upd:

In case you faced password validation error you can override User#password_required? method to skip validation for user's signed in via Facebook.

That behavior described in following episode of RailsCasts

Upvotes: 4

Related Questions