I am following Ryan's instruction on Omniauth here.
However, there is a scenario that Ryan didn't cover:
User already have an account with us, but they signin with Facebook/Linkedin for the first time. In this case, I would like the user to sign-in using traditional sign-in (via Devise), and if he succeeds, create a new Authenication object for future logins.
In particular, when registered user clicks "Signin with Facebook" for the first time, I'll get a hash from callback that contains his Facebook account information. But I don't want to create an authentication for him yet. Instead, I will redirect him to my Devise sign-in page, allowing him to put username and password in. If he signs in successfully, I would like to create a new Authentication object from the information I got from Facebook callback.
What would be a good way to store Facebook hash temporarily, until user logs in successfully?
Thank you.
Below is the code from AuthenticationController.rb that Ryan created, which did not handle the situation I just mentioned:
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth
redirect_to new_user_registration_url
end
end
end
Instead of doing this, could you just check to see if there is another user with the same email when they register with facebook? If so, add facebook as a second method of authentication, and if not, create a new account.
Related
I've gotten the omniauth to work with google by following this tutorial. The problem I'm currently having is that instead of creating the user when they sign up, I want to route them to a finish registration page where they have to enter additional data. This is similar to how pastebin handles their oauth registration.
UserModel (taken from tutorial):
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:provider => access_token.provider, :uid => access_token.uid ).first
if user
return user
else
registered_user = User.where(:email => access_token.info.email).first
if registered_user
return registered_user
else
user = User.create(name: data["name"],
provider:access_token.provider,
email: data["email"],
uid: access_token.uid ,
password: Devise.friendly_token[0,20],
)
end
end
end
omniauthCallBacksController
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
It looks like the problem is that your find_for_google_oauth2 is creating the user. This means that the else in your google_oauth2 callback isn't getting hit, so it'll never go to your new_user_registration_url. One solution is to separate your find and create steps rather than combining them in find_for_google_oauth2. Then, when the find doesn't find an existing user, you hit your else in your google_oauth2 callback and the omniauth data is put in the session and you get sent to new_user_registration_url. Then once they've entered the extra info and submit the form, you can use that, combined with the omniauth data you saved in the session, to create the user.
Two things about putting the omniauth data in the session:
You probably want to pick what you want to keep from the omniauth hash (especially if you're using CookieStore for the session storage) as it's quite large.
If you're using the CookieStore and Rails < 4 then the omniauth data being stored in the session (i.e. in the cookie on the user's computer) is unencrypted.
I have seen casts. Understand everything, But how to perform 2 in 1 auth with social networks not using Devise? What should be in SessionsController, action create? Or I need to create specific action?
For example in such controller, how to check in what way user wants to auth
def create
user = User.authenticate(params[:email], params[:password])
if user
session[:user_id] = user.id
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
Here is a detailed explanation of implementing this
http://railscasts.com/episodes/241-simple-omniauth?view=asciicast
Just download the source and run the app for better understanding
Please read this blog post to understand how to integrate Rails app with other Social Network such as Facebook and Twitter - http://www.manaslutech.com/blogs/3-Ruby-on-Rails-integration-with-Facebook-and-Twitter
I am trying to fetch the list of friends from Facebook. Sign in through Facebook is not a problem, but the problem is to fetch person's friends - because of access token.
puts request.env["omniauth.auth"].inspect
puts '==='
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
#fb_user = FbGraph::User.fetch(#user.uid).friends
puts #fb_user.inspect
The problem is on the #4 line - in this case I am getting error
OAuthException :: An access token is required to request this resource.
When I put there something like this:
#fb_user = FbGraph::User.fetch(request.env["omniauth.auth"].credentials.token).friends
I'll get
OAuthException :: (#803) Some of the aliases you requested do not exist: PRINTED OUT TOKEN
What's the proper way to obtain the access token?
EDIT: Current flow
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
#fb_user = FbGraph::User.fetch(request.env["omniauth.auth"].credentials.token).friends
if !#user
flash[:error] = 'This email address is already used in the system.'
redirect_to :back
elsif #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
In User model:
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = User.where(:provider => 'facebook', :uid => data.id).first
user
elsif user = User.where('email = ? AND provider IS NULL', data.email).first
return false
else
...saving data...
end
return user if user
end
You can get an access token for test purposes via the Facebook Graph API Explorer. Make sure you select the proper fields that you want access to, and click "get access token". A more permanent solution is to register your app with Facebook so that you will be able to continually make requests without the token dying.
You should look into the Facebook OAuth dialogue.
I'm assuming you're trying to use the OAuth2 strategy instead of the Javascript SDK. Make sure you have set up a callback url like so:
client.redirect_uri = "http://your.client.com/facebook/callback"
In the controller that handles your callback, you should do something like this:
client.authorization_code = params[:code]
access_token = client.access_token! :client_auth_body
FbGraph::User.me(access_token).fetch
Make sure you've let fb_graph know what your app's id and secret are. You should look into this stackoverflow to keep your apps info safe.
I'll also plug the koala gem
We have registration and login via facebook using rails, devise, and omniauth. We're hosted on Heroku, running two web dynos.
sometimes login with facebook is failing. The actual handshake between our app and facebook is fine. In fact, in the code below #user is an actual User model instance, the omniauth data Hash contains all of the data from FB, sign_in seems successful AND the current_user is set.
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.find_for_facebook(env["omniauth.auth"].extra.raw_info, current_user)
logger.info ">>>>>> OMNIAUTH FB BEGIN >>>>>>"
logger.info env["omniauth.auth"].inspect
logger.info "User is: #{#user.inspect}"
session["devise.facebook_data"] = request.env["omniauth.auth"].except("extra")
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
res = sign_in(:user, #user)
logger.info "Sign In Result: #{res.inspect}"
logger.info "CURRENT_USER: #{current_user.inspect}"
logger.info "<<<<<< OMNIAUTH FB END <<<<<<"
respond_to do |format|
format.json
end
end
end
The client-side does a redirect (different actions based on login context) to another URL on our site upon successful login. At that URL, we check for current_user... but current_user is returning nil. If the login process is repeated a few times, eventually it works.
We're on
Rails 3.2
Devise 2.1.2
Omniauth-facebook 1.4.0
Ruby 1.9.3 p194
I don't know whether this is really the solution to what you've described, but I wonder whether this:
#user = User.find_for_facebook(env["omniauth.auth"].extra.raw_info, current_user)
should read as this?
#user = User.find_for_facebook(request.env["omniauth.auth"].extra.raw_info, current_user)
that's how mine is anyways. NOt really sure that would explain why your app is arbitrarily letting the user sign in however.
I am currently using this guide to try to integrate twitter into Devise.
It is a little challenging because twitter's OAuth does not provide email addresses. Hence the flow of the sign up should be:
User clicks "Sign in with twitter"
Oauth call back to twitter's callback
Ask for the user for email (I need that for my site)
Sign in user.
I realized that if the user already has an account on my system with Twitter, I must be able to find the account. Hence I have added 2 extra field to the user model: oauth_provider, oauth_uid.
In omniauth_callbacks_controller:
def twitter
#user = User.find_for_twitter_oauth(env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
sign_in_and_redirect #user, :event => :authentication
else
flash[:warn] = "We still need a little more info!"
redirect_to new_user_registration_url
end
end
In user.rb
# The trick here is that twitter does not give you an email back
# So we should make use of uid and provider
def self.find_for_twitter_oauth(oauth_hash, signed_in_resource=nil)
uid = oauth_hash['uid']
if user = User.find_by_oauth_provider_and_oauth_uid('twitter', uid)
user
else
User.create(:password => Devise.friendly_token[0,20],
:oauth_provider => "twitter",
:oauth_uid => oauth_hash['uid'])
end
end
However, I have debugged this thoroughly and realized that if I redirect a user to new_registration_url, the User created in user.rb will be wiped.
How can I do the following:
If user cannot be found via oauth_provider and oauth_uid, create a User object with these credentials
direct user to new_registration_url
When the user have submitted his/her email, create the user with the same user object created in 1)
I have tried using session, but it gets really messy as I have to monkey patch devise's new and create for registrationscontroller.rb.
Please someone provide me a way to do this.
I have not been successful yet. Let me show you what I have written.
I followed these 2 screencasts and it is exactly what you want.
You can try it out! He is using the omniauth gem, which is very easy and awesome :-)
http://railscasts.com/episodes/235-omniauth-part-1
http://railscasts.com/episodes/236-omniauth-part-2