The example sorcery code shown on github appears to me to create duplicate accounts if it is extended to allow for multiple sign in methods (which is the whole point of oauth). You can see in the snipit here that create_from() will be called if login_from() does not succeed.
GITHUB AT at https://github.com/NoamB/sorcery-example-app/blob/master/app/controllers/oauths_controller.rb
def callback
provider = params[:provider]
begin
if @user = login_from(provider)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
else
begin
@user = create_from(provider)
Investigating the source code for create_from in all cases a new User Account record will be created. This would not be correct, if a User account record already exists.
My question: What sorcery methods should be called on the first facebook connect, if a User account has been created by some means other than facebook. login_from will fail, and create_from will generate a duplicate usser record?
Several requests have come through for an answer to this question, so I am providing the answer that Andy Mejia part of my team eventually arrived at for this question. We used the source within sorcery to adapt the following functions:
# Returns the hash that contains the information that was passed back from Facebook.
# It only makes sense to call this method on the callback action.
#
# Example hash:
# {:user_info=>{:id=>"562515238", :name=>"Andrés Mejía-Posada", :first_name=>"Andrés", :last_name=>"Mejía-Posada", :link=>"http://www.facebook.com/andmej", :username=>"andmej", :gender=>"male", :email=>"[email protected]", :timezone=>-5, :locale=>"en_US", :verified=>true, :updated_time=>"2011-12-31T21:39:24+0000"}, :uid=>"562515238"}
def get_facebook_hash
provider = Rails.application.config.sorcery.facebook
access_token = provider.process_callback(params, session)
hash = provider.get_user_hash
hash.merge!(:access_token => access_token.token)
hash.each { |k, v| v.symbolize_keys! if v.is_a?(Hash) }
end
# Method added to the User Account model class
def update_attributes_from_facebook!(facebook_hash)
self.first_name = facebook_hash[:user_info][:first_name] if self.first_name.blank?
self.last_name = facebook_hash[:user_info][:last_name] if self.last_name.blank?
self.facebook_access_token = facebook_hash[:access_token]
self.email ||= facebook_hash[:user_info][:email]
unless facebook_authentication?
authentications.create!(:provider => "facebook", :uid => facebook_hash[:uid])
end
self.build_facebook_profile if facebook_profile.blank?
save!
self.facebook_profile.delay.fetch_from_facebook! # Get API data
end
To show these code in context, I am also including logic from our controller:
def callback
provider = params[:provider]
old_session = session.clone # The session gets reset when we login, so let's backup the data we need
begin
if @user = login_from(provider) # User had already logged in through Facebook before
restore_session(old_session) # Cleared during login
else
# If there's already an user with this email, just hook this Facebook account into it.
@user = UserAccount.with_insensitive_email(get_facebook_hash[:user_info][:email]).first
# If there's no existing user, let's create a new account from scratch.
@user ||= create_from(provider) # Be careful, validation is turned off because Sorcery is a bitch!
login_without_authentication(@user)
end
@user.update_attributes_from_facebook!(get_facebook_hash)
rescue ::OAuth2::Error => e
p e
puts e.message
puts e.backtrace
redirect_to after_login_url_for(@user), :alert => "Failed to login from #{provider.titleize}!"
return
end
redirect_to after_login_url_for(@user)
end
I hope this solution is helpful to others.