Link facebook to existing account / omniauth - ruby-on-rails

Simple as that, I want to link to an existing user account his facebook profile (giving priority to the email that has originally registered with) in my rails app, using omniauth and devise.
I have read this but wasn't much helpful to me.
My current structure is like this one.

Below is an example of how I implemented this. If the user is already signed in, then I call a method that links their account with Facebook. Otherwise, I go through the same procedure outlined in the Devise-Omniauth wiki page.
# users/omniauth_callbacks_controller.rb
def facebook
if user_signed_in?
if current_user.link_account_from_omniauth(request.env["omniauth.auth"])
flash[:notice] = "Account successfully linked"
redirect_to user_path(current_user) and return
end
end
#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?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
# app/models/user.rb
class << self
def from_omniauth(auth)
new_user = where(provider: auth.provider, uid: auth.uid).first_or_initialize
new_user.email = auth.info.email
new_user.password = Devise.friendly_token[0,20]
new_user.skip_confirmation!
new_user.save
new_user
end
end
def link_account_from_omniauth(auth)
self.provider = auth.provider
self.uid = auth.uid
self.save
end

Related

How do I know when a user is registering vs logging in with Devise and Omniauth in Rails

I'm using Rails 6 + Devise for login/registration. Users can register/login with Facebook via omniauth.
I want to log one analytics event when the user logs in, and a different analytics event when they register for the first time.
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
flash[:log_event] = {
'event_category' => 'engagement',
'event_name' => 'login',
'method' => 'facebook'
}
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path
end
end
The flash[:log_event] is passed to Google Analytics. My problem is that Devise seems to follow the same code path for first registration as it does for a regular login.
I suppose I could check the #user.created_at timestamp, and treat it as a registration if it's a couple of minutes old, but I'm sure there's a cleaner solution.
You can always make your own version of User.from_omniauth. Notice the first_or_initialize instead of ..._create
# in app/models/user.rb
def self.from_omniauth(auth)
user = where(auth.slice(:provider, :uid)).first_or_initialize do |new_user|
new_user.provider = auth.provider
new_user.uid = auth.uid
new_user.username = auth.info.nickname
end
user.image_url = auth.info.image # need to update in case image changed on provider's site
user
end
Then in your controller check for new_record?
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.new_record?
#user.save # Important step I missed earlier
# ... Do some custom thing here for your app
end
if #user.persisted?
# ....

Login with Facebook when user is registered, Rails 5, Omniouth with Device

I have this User model:
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.fname = auth.info.name
user.skip_confirmation!
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] &&
session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end
and this omniouth_callbacks_controller:
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
#user.confirmed_at = Time.now
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
Everything works, but if user is registered without Facebook and then try to login with Facebook, it is not logged, because there are user row in the table in the database.
I want if user is registered in the site without socials and login with Facebook, to do that and site profile to be connected with Facebook profile.
Is there Omniouth method builtin for this case ?
If not, how to do it ?

sign in VS sign up | devise with omniauth-facebook

Rails 5.0.0.1
Ruby 2.3.1p112
gem 'devise'
gem 'omniauth'
gem 'omniauth-facebook'
I have a app working with omniauth-facebook and Devise which must have some required extra attributes on signup page. It works very well but I'm having some difficulty on breaking authentication into two situations/behaviours:
The specific page for user sign up by requiring some extra params.
The specific page for user sign in (if the user already signed up).
The problem is on first_or_create method.
I need to create a record only on sign up page with my required params, otherwise, on the sign in page, I just need check if the user already signed up and redirect to sign up page if necessary.
Am I clear?
Model:
class User < ActiveRecord::Base
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
user.avatar = auth.info.picture
user.username = auth.info.name.parameterize.dasherize.first(10) + Digest::SHA2.hexdigest("#{Time.now.utc}").first(5)
user.skip_confirmation!
end
end
end
Callback:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
params = request.env["omniauth.params"]
#user.username = params["username"]
#user.newsletter = params["newsletter"]
#user.diet_id = params["diet_id"]
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
def failure
redirect_to root_path
end
end
Relevant code for signup page:
= form_tag user_facebook_omniauth_authorize_path(newsletter: #user.newsletter, username: #user.username, diet_id: #user.diet_id) do
Relevant code for sign in page:
= link_to "Sign in with Facebook", user_facebook_omniauth_authorize_path

Integrate facebook login for already registered users

I am integrating facebook login in my application using the example code from the Omniauth documentation
https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
def self.from_omniauth(hash)
where(email: hash.info.email).first_or_create do |user|
user.email = hash.info.email
user.password = Devise.friendly_token[0,20]
user.username = hash.info.name # assuming the user model has a name
end
end
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# render :text => request.env['omniauth.auth'].to_yaml
# 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?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
I followed the example as it is. However as the code in omniauth_callbacks_controller I'd say if an user is a new to the application then it should follow the else statement and be redirected to user registration url. However what happens is that a new user is created in the database always. Shouldn't I fall in the else statement in case the user has no account yet in my app?
According to the suggested implementation of the method from_omniauth I understand that first_or_create actually creates a new user in the db using the information from facebook. But if that's the case, what is the use of the else option redirecting to new_user_registration_url?
Anything I am not seeing?

How to save the request.referrer for facebook omniauth in Rails 4

I have two facebook buttons in my application. One on users/sign_up and other on /visitors/owner-faq. I want to save the request.referrer in order to know from which page the user has signed up. I had implemented the same for external signup(which uses a form) and it worked. Now I'm unable to implement the same for facebook omniauth.
Code:
#user.rb
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by_email(email) # first tries to find an existing user who signed up normal way with the same email to sign them in
if user && user.confirmed?
user.provider = auth.provider
user.uid = auth.uid
return user
end
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| # then tries to find the user who authenticated through FB, and if
user.email = auth.info.email # not present, creates that user
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.social_photo_url = auth.info.image
user.skip_confirmation!
end
end
#users/omniauth_callbacks_controller
def facebook
#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?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
Rails.logger.info(#user.errors.inspect)
redirect_to new_user_registration_url
end
end
#application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :store_referrer_url, :only => [:new]
private
def store_referrer_url
session[:referrer] = URI(request.referer).path
end
end
Attempt #1:
I managed to save the request.referrer like this in users/omniauth_callbacks_controller
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
#user.referrer_url = session[:referrer] #here
#user.save!
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?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
Rails.logger.info(#user.errors.inspect)
redirect_to new_user_registration_url
end
end
But the problem here is the value of referrer_url is overwritten when the existing user logs in from another page. I don't want to get the referrer_url to be overwritten.
Attempt #2:
I tried to save the request.referrer in the from_omniauth(auth) method User model like this
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by_email(email) # first tries to find an existing user who signed up normal way with the same email to sign them in
if user && user.confirmed?
user.provider = auth.provider
user.uid = auth.uid
return user
end
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| # then tries to find the user who authenticated through FB, and if
user.email = auth.info.email # not present, creates that user
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.social_photo_url = auth.info.image
user.referrer_url = session[:referrer]
user.skip_confirmation!
end
end
But it gave me this error
undefined local variable or method `session' for #<Class:0x0000000e1c46f8>
Any suggestions would be greatly helpful.
If you want the referrer_url property to stay the same after it's first set, you should use the OR Equal operator, that will only set the value if it's currently nil or false:
#user.referrer_url ||= session[:referrer]
If I understand correctly, your facebook callback is called both on sign up and login, and of course the value would be overwritten. OR Equals will prevent the value from being overwritten once it's set.
Regarding Attempt #2, the session hash is only available in the controller and the view, so you can't use it in the model.
Another possibility:
You are setting session[:referrer] in your store_referrer_url method which is called in a before_filter callback on the new method.
Most likely your login is also made with a new method (for example, with Devise it would be in SessionsController), so the callback is called again and the value is overwritten (all Devise controllers inherit from your ApplicationController). In this case, you could take out the before_filter callback out of the ApplicationController to the controller where you are handling signing up (would be RegistrationsController with Devise).

Resources