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
Related
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.
I am using omniauth to let people sign up/sign in with Facebook and its working well ! But I wanted to add the omniauth-twitter gem to let them connect with Twitter.
I followed the same steps than when I set up the Facebook connect: https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
But when I signing up/in I get the following error:
ActionDispatch::Cookies::CookieOverflow in OmniauthCallbacksController#twitter
at the following URL:
http://localhost:3000/users/auth/twitter/callback?oauth_token=HRjON8J4bj9EcbjiELHcpHmSXo0cPd0wCHyuWG8ATZU&oauth_verifier=ZiZb1FAKZmNML1gVu5RKBLEGzbeAPPzC80QCpPDGU
I tried different things suggested on similar posts but none of these worked :(
Here is my configuration:
omniauth_callbacks_controller.rb => app/controllers/omniauth_callbacks_controller.rb
def twitter
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_twitter_oauth(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 => "twitter") if is_navigational_format?
else
session["devise.twitter_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
user.rb => app/models/user.rb
def self.find_for_twitter_oauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.name = auth.info.name # assuming the user model has a name
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.twitter_data"] && session["devise.twitter_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end
devise.rb => app/config/initializers/devise.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, "KEY, "KEYPASSWORD
end
Any ideas what's wrong?
As Michael says in the comments, you're storing a large hash in the session and it's too big (you're using the default CookieStore and cookies can only contain 4KB of data). That hash provided by omniauth has all the data returned by twitter, which can be quite a lot. For example, see the README: https://github.com/arunagw/omniauth-twitter#authentication-hash
If the code in your question is all the code relating to twitter login, then it looks like you only need to keep the email in the session as that is all that is used by your new_with_session code. So your line in the else in twitter which is:
session["devise.twitter_data"] = request.env["omniauth.auth"]
could be something like:
session["devise.twitter_data"] = request.env["omniauth.auth"].select { |k, v| k == "email" }
However the major flaw with this is that twitter doesn't return an email address for a user, so data["email"] will always be nil in new_with_session anyway! So it's pointless keeping anything in the session if you are only later interested in the email which is never returned by twitter. Perhaps you instead want to retrieve a name to help prefill the registration form instead of the email address. In this case, you could just keep that in the hash from omniauth. If you want to keep a few things in the hash, then instead of selecting them all to put in the session, you could do something like:
session["devise.twitter_data"] = request.env["omniauth.auth"].delete_if("extra")
which will remove the "extra" nested hash which could help everything else to fit in the session.
For a complete solution you'll have to consider messy situations like dealing with people who have signed in with Facebook and then come and sign in with Twitter and want to use the same email address and merge with their existing account on your system.
In any case, note that if you are using Rails 3 then the session cookie is not encrypted so the user or anyone with access to their computer could read the contents of the cookie with whatever data from twitter you end up keeping in there. If you're using Rails 4, then the cookie should be encrypted to protect against that.
Is it possible to only allow certain google accounts to log on? for example myname#mycompany.com is host through google (they are actually google account). I want only user with the #mycompany to be able log on is this possible?
do you do this with devise or google api?
Thank you :)
If you are using omniauth-google-oauth2, you can accomplish domain restrictions using by providing a value for hd option during initialization.
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], {
scope: 'email, profile',
hd: 'mycompany.com'
}
end
It's also possible to handle this in your controller which is handling the callback. You can deny users depending on values provided in request.env["omniauth.auth"].
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
auth_details = request.env["omniauth.auth"]
if auth_details.info['email'].split("#")[1] == "yourdomain.com"
# do all the bits that come naturally in the callback controller
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in Through Google!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
flash.notice = "You are almost Done! Please provide a password to finish setting up your account"
redirect_to new_user_registration_url
end
else
# This is where you turn away the poor souls who do not match your domain
render :text => "We're sorry, at this time we do not allow access to our app."
end
end
end
I've seen a couple people ask a similar question, but I really need advice on how to debug this issue. I'm trying to setup facebook connect using Devise using the article here: https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
Every time I click on the login with facebook link, I get the blank page that just says: Not found. Authentication passthru. Clearly, there is no JavaScript/ajax setup on the prior page to pull up the facebook login screen.
I know this can work on my system, as I made a blank project with the exact same code from the link above and it works. Of course, my project is much bigger with lots of code, so I'm trying to figure out what in my project is causing this not to fire.
Any help on how to debug is appreciated.
Thanks!
This is the #passthru code from the devise source.
def passthru
render :status => 404, :text => "Not found. Authentication passthru."
end
Which means that devise is unable to recognize your facebook callback. Make sure you setup up your callback controller properly or post your user controller code.
I ran into the same error. I was missing the Facebook callback controller (app/controllers/users/omniauth_callbacks_controller.rb):
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# ...
def facebook
respond_to do |format|
format.html {
#user = User.find_for_facebook(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user
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
end
This code references the method "find_for_facebook" in my user model (app/models/user.rb):
class User < ActiveRecord::Base
# ...
def self.find_for_facebook(auth_hash)
user = User.where(:email => auth_hash.info["email"]).first
unless user
user = User.create(
username: auth_hash.info["nickname"],
email: auth_hash.info["email"],
password: Devise.friendly_token[0,20])
end
user.provider = auth_hash["provider"]
user.uid = auth_hash["uid"]
user
end
end
Make sure to restart your development server so all the changes get picked up.
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