Devise Omniauth, how to link google account - ruby-on-rails

I've set up devise to work with omniauth and. This is how devise.rb looks like:
...
config.omniauth :facebook, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, :scope => FACEBOOK_APP_PERMISSIONS
config.omniauth :openid, OpenID::Store::Filesystem.new('./tmp'), :name => 'yahoo', :identifier => 'yahoo.com'
config.omniauth :openid, OpenID::Store::Filesystem.new('./tmp'), :name => 'gmail', :identifier => 'https://www.google.com/accounts/o8/id'
...
I want to link an existing account with the the above 3, with the following code from the callback controller:
...
def callback(provider)
if utilizator_signed_in?
# link it's account
if Login.link_omniauth(current_utilizator, omniauth_data)
flash[:notice] = I18n.t "devise.omniauth_callbacks.link.success", :kind => provider
redirect_to :root
end
else
utilizator = Login.auth_with_omniauth(omniauth_data)
if !utilizator.nil?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => provider
sign_in_and_redirect utilizator, :event => :authentication
else
utilizator = Login.register_omniauth(omniauth_data)
flash[:notice] = I18n.t "devise.omniauth_callbacks.register.success", :kind => provider
sign_in_and_redirect utilizator, :event => :authentication
end
end
end
...
It works just great with facebook, but for google and yahoo the current user get's signed out and a new user is created.
How can I skip the user sign_out phase ?
Thank you,
Edit:
I am using the latest version 3.0.rc3.
The functions from Login are easy to guess:
link_omniauth - will link the current logged user to a account
auth_with_omniauth - will auth the user
register_omniauth - will register a new user
The problem here is that utilizator_signed_in? (user_signed_in?) will return false for google when the user is signed in, I think there is a prior sign_out happening that is not happening for facebook.

I highly recommend this tutorial: here (far more in-depth than the rails casts and other tutorials on the topic). In particular, it has a comprehensive 200 line piece of code (services_controller.rb) to create the controller that you will need to handle any authentication service (Twitter/Facebook/Google/Github) efficiently using Omniauth, and either link it to pre-existing Devise accounts, or create new accounts.
I have a (almost) live project running with that here -
You can sign in using Facebook/Twitter (I haven't enabled google/github for now), and if you go to Profile->Services after you are signed in, it will show you the multiple authentication services linked to your account.
I'm reluctant to upload my project's source since it has a lot of other code that isn't relevant to this at all, but if you really feel like you need it, then tell me.

So I finally found the answer to my question.
This guy https://github.com/intridea/omniauth/issues/185 had the same issue.
Thanks for your replies,

I hope already submit my gmail address link to my profile task into my application

Related

Omniauth Callback

Im using devise with my rails 4 app. I authenticate with Facebook, LinkedIn and email.
I've just started to use Figaro and the only change I have made to my code is to swap the password that I use for my email account out of the production.rb into my application.yml file.
Now, when I test the LinkedIn registration link, I get an error saying that something went wrong (after pressing "Register with LinkedIn"). I get the same error when I try to authenticate with the other options.
I have a callback error in my omniauth callback controller for linkedin. The line with the problem is the '#user.send_admin_email' below:
def linkedin
#user = User.find_for_linkedin_oauth(request.env["omniauth.auth"])
if #user.persisted?
#user.send_admin_mail
redirect_to root_path, :event => :authentication
# sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
# set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
else
session["devise.linkedin_data"] = request.env["omniauth.auth"]
redirect_to root_path
end
end
I have a mailer set up which sends me an email to tell me when there is a new registration. It uses the email address for which I moved the password from production.rb to application.yml
Does anyone know how to resolve this error?
Thank you very much
It is possible that the problem is the text-encoding of your application.yml file, especially if your email password has non-standard characters in it. Try firing up the console, and just output your password as it looks with to_s
p ENV['your_figaro_key'].to_s
See if this returns what you would expect
Hmm, it seems a "strong parameter" problem to me. Check your "send_admin_mail" to see if there is a "update_atributes" (or a "save").
My guess is that your user.rb is something like:
class User < ActiveRecord::Base
...
attr_accessor :extras
def self.find_for_linkedin_oauth(access_token, signed_in_resource=nil)
...
user = User.where(...)
...
user.extras = access_token.extras
...
end
def send_admin_mail
...
self.update_attributes(some_attribute: extras["some_attribute"])
...
end
If you are doing this, "save" will try to do an UPDATE with a non-permitted parameter. The correct version must be something this:
self.update_attributes(some_attribute: extras.permit(:some_attribute))
If I'm not wrong, the first implementation worked in the previous versions of strong parameters, but not anymore.

Rails devise omniauth-facebook .persisted? returning false

I've spent the last day trying to fix this issue and it's driving me nuts.
Last night, I had facebook login working on my site and was retrieving basic user info. My problems started when I added :scope => 'user_birthday' to config/initializers/omniauth.rb which now looks like this
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, "APP_ID", "APP_SECRET", :scope => 'user_birthday'
end
For reference, I've removed the line config.omniauth :facebook, "APP_ID", "APP_SECRET" from config/initializers/devise.rb
I spent hours trying to get this to work but had to give up eventually. Then this morning I ran it again and it worked. Overjoyed, I tried to add another parameter to :scope but now the whole thing is broken again. I can get it to work if I remove the :scope but when I put it back in it fails every time (even if it's just :scope => 'user_birthday' like I had working first thing this morning).
To locate the problem, I put debug code in omniauth_callbacks_controller.rb and it now looks like:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.from_omniauth(request.env["omniauth.auth"])
puts "start before persist debug"
puts #user.birthday
puts #user.persisted?
puts "end before persist debug"
if #user.persisted?
puts "Start persisted debug"
puts request.env["omniauth.auth"]
puts "End debug"
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"]
puts "Start unpersisted debug"
puts request.env["omniauth.auth"]
puts "End debug"
redirect_to new_user_registration_url
end
end
end
This debug clearly shows that facebook is returning the necessary information but the app is failing because .persisted? is returning false and so I get re-directed to the new_user_registration page which returns the following:-
NoMethodError in Devise::Registrations#new
Showing /home/action/workspace/cloudapp/app/views/devise/shared/_links.html.erb where line #23 raised:
undefined method `omniauth_authorize_path' for #<#<Class:0x007f3aeffff038>:0x007f3aefffdf08>
I can't for the life of me figure out why .persisted? is returning false. I'm using Nitrous.io for development with a Heroku postgresql database. I've confirmed there are no users in the database by running
rails c
User.all
This returns:
User Load (89.4ms) SELECT "users".* FROM "users"
=> #<ActiveRecord::Relation []>
I have a feeling the problem is in models/user.rb but I can't figure out how to debug it to see if it's finding a user and therefore not persisting or trying to create one and failing. Does anyone know a simple way to debug this?
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 # assuming the user model has a name
user.birthday = auth.extra.raw_info.birthday
# user.image = auth.info.image # assuming the user model has an image
end
end
I've gone over everything about 50 times and am close to giving up.
The only thing I can think of is that where(provider: auth.provider, uid: auth.uid) is returning something (which it shouldn't because my database is empty). Would there possibly be an index that exists somewhere outside my database and that's what it's searching?
Please, for my sanity, can anyone help? If you need more info I'll gladly provide it
Edit 1
Just tried the following and it works which make me more confused than ever:
Delete the app from my facebook account as I'm testing using that account
Try to log in with facebook with :scope => 'user_birthday' left in. Facebook lists the permissions sought as your public profile and birthday. Accept and get sent back to my site which fails as per above (even though the info is definitely being sent back)
Remove :scope => 'user_birthday' and try log in using facebook again. Get directed to facebook which lists permission sought as your public profile and email address. Accept and get directed back to site which now works and also has the user birthday stored and accessible because I had the permisision from facebook from number 2 above.
I'm completely at a loss now
To find out about why is the object not being saved. You need to puts the errors.
puts #user.errors.to_a
And to check the content of the auth
puts request.env["omniauth.auth"]
I had the same problem and follow the answer above and I put "#user.errors.to_yaml" on my code to I see where was the error and I found.
I am using "devise" and "omniauth-facebook" too. The default scope of the omniauth-facebook is "email". However, I put on the scope the properties: "user_about_me, user_status, user_location, user_birthday, user_photos". I need to add "EMAIL" on the scope to devise to use on creation of the 'user'. I discover this when I saw my error: "email don't be blank".
Summary:
If you insert properties on the scope, ALWAYS put "email" too.
Facebook not always returning email for user
from facebook developers https://developers.facebook.com/bugs/298946933534016
Some possible reasons:
No Email address on account
No confirmed email address on account
No verified email address on account
User entered a security checkpoint which required them to reconfirm
their email address and they have not yet done so
Users's email address is unreachable
You also need the 'email' extended permission, even for users who have a valid, confirmed, reachable email address on file.
User entered a security checkpoint which required them to reconfirm their email address and they have not yet done so
Users's email address is unreachable
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
puts request.env["omniauth.auth"] # check if request.env["omniauth.auth"] is provided an email
if request.env["omniauth.auth"].info.email.present?
#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
else
redirect_to new_user_registration_url, notice: "Can't sign in to your #{request.env["omniauth.auth"].provider} account. Try to sign up."
end
end
end

OmniAuth Facebook added to Devise

I'm using Rails 4.1 and already have devise configured but now would like to add sign up through Facebook and Twitter. Devise is fully set up and working for a user to sign up via email.
I've gone ahead and added the "omniauth-facebook" gem to my gemlist.
I've set up my api key and api secret
#config/initializers/devise.rb
config.omniauth :facebook, "API KEY", "API SECRET"
My Route file
#routes.rb
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks", :registrations => "registrations" }
My omniauth_callbacks_controller.rb
def facebook
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
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
and my user model
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
registered_user = User.where(:email => auth.info.email).first
if registered_user
return registered_user
else
user = User.create(name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20],
)
end
end
end
I also added the link to the signup page but when i click on the button I'm getting the following error
{
error: {
message: "Invalid redirect_uri: Given URL is not allowed by the Application configuration.",
type: "OAuthException",
code: 191
}
}
It looks like Facebook is objecting to the url you've told it to redirect to after someone has logged in via Facebook. Check what you've specified in your Facebook app setup in "Manage Apps" -> Your App via your Facebook developer account. Check that one of your App Domains on the Settings->Basic tab matches the domain you want to redirect to after Facebook login. Also check that you're redirecting to a URL you've specified in "Valid OAuth redirect URIs" (if any) on the Settings -> Advanced tab.
My relevant config (with real domain name changed) is as follows:
On Settings -> Basic:
App Domains: mydomain.com
A web platform with URL: http://www.mydomain.com/
On Settings -> Advanced:
Client OAuth login: YES
App Secret Proof for Server API calls: YES (optional, but I like security and I don't think it'll exacerbate your problem - conversely, if you've got it as NO, then I don't think it'll matter if you leave it like that for the purposes of this problem)
Valid OAuth redirect URLs: https://www.mydomain.com/users/auth/facebook/callback https://staging.mydomain.com/users/auth/facebook/callback http://dev.mydomain.com:3000/users/auth/facebook/callback
So you can see I allow redirects to production, staging and dev environments. Your paths might vary depending on how you've set up your routes.
Then, I add an alias for the dev domain to my /etc/hosts file:
127.0.0.1 localhost dev.mydomain.com
so when Facebook tells my browser to redirect to dev.mydomain.com, it goes to the rails app on my machine.
If you specify the redirect urls, you should double-check that you're definitely supplying one of them to Facebook when you send the user there when they click on the button (I found devise/omniauth needed a bit of bludgeoning to get the paths as I wanted).

Integrating Devise with Twitter

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

Omniauth+Facebook lost session

In a recent project, facebook Users can login using their Facebook UID to upload picture submissions based on file uploads or uploads from their personal albums etc.
Everything works quite nice on my local system in the development environment. Login via Facebook, Logout, Upload - all great.
In production though I'm facing a unknown and hard to debug problem. It seems that every once in a while (actually reproducable when uploading a new Submission to the system) the session is lost, the picture is NOT uploaded and the facebook user is logged out (!).
I'm using devise and omniauth. Omniauth is integrated into Devise.
Following is all the code that touches Devise/Omniauth or the User.
app/models/user.rb
class User < ActiveRecord::Base
devise :omniauthable, :rememberable, :omniauth_providers => [:facebook]
def self.create_with_omniauth(auth)
u = User.find_by_uid(auth["uid"])
return u unless u.nil?
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["user_info"]["name"]
user.email = auth['user_info']['email']
end
end
def after_signin_path
'/competition'
end
end
Database contains all needed fields for :rememberable, I hope.
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model
#user = User.create_with_omniauth(env["omniauth.auth"])
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
#user.update_attributes!(:current_auth_token => env["omniauth.auth"]['credentials']['token'], :last_language => I18n.locale.to_s, :updated_at => Time.now, :remember_created_at => Time.now)
sign_in_and_redirect(:user, #user)
else
redirect_to '/competition'
end
end
protected
def after_omniauth_failure_path_for resource
'/competition'
end
end
config/initializers/devise.rb
OmniAuth.config.full_host = "http://#{APP_CONFIG[:domain]}"
Devise.setup do |config|
config.mailer_sender = "devise#myapp.host.com"
require 'devise/orm/active_record'
config.stretches = 10
config.encryptor = :bcrypt
config.timeout_in = 3.days
config.pepper = "2a4b8b2ed9e12e553a7a542176f2ace1af62c062f3ba203a590b8b6307f33042b394922807a840004a3dcdf1c4e97ae085fe2c29654ddaeab7c60f431a8078abb"
config.omniauth :facebook, APP_CONFIG[:facebook_app_id], APP_CONFIG[:facebook_app_secret], {
:scope => "email,user_photos,user_photos,publish_stream,offline_access",
:client_options => {
:ssl => {
:ca_file => "/etc/pki/tls/certs/ca-bundle.crt"
}
}
}
end
There are no auth-related methods in application_controller.rb.
routes.rb:
The interesting part below:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
match '/logout_fb' => 'start#logoutfb'
authenticate :user do
get '/users/connect/:network', :to => redirect("/users/auth/%{network}")
end
Somehow I cannot get to understand the authenticate block, which according to another post should be helpful.. ideas on this too?
So many theories:
One is that the facebook function in the omniauth_callbacks_controller runs aside of the users' session, and hence sign_in_and_redirect won't work. So I had the idea of redirecting to another page like '/auth?uid=xxx' but this sounds both wrong, insecure and not stable.
Any help or hints are appreciated!
A bit of a long shot but try turning off protect_from_forgery - I had some issues with sessions disappearing and it turned out to be the issue discussed here https://github.com/intridea/omniauth/issues/203
In my config/initializers/omniauth.rb, I had to add the following:
OmniAuth.config.full_host = "http://yourdomain.com" # Or have an environment specific URL.
You are using devise but you are not using it's own helpers. For instance, you've defined your own current_user method. To be honest, I can't see any obvious mistakes you've made, so it's just a desperate tip.
what kind of a session store do you use locally and what in production?
When you say "facebook user is logged out", this user is still logged in to facebook, but lost his session at yourapp.com ?
Are you sure that user.id is never nil or that you anywhere else than in .destroy set session[:user_id]= some_nil_variable ?

Resources