Reputation: 1548
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
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
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
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