I've trying to manage user sign up with google account for my rails 4.0.0 app. Devise works perfectly. And there is working sign in with Google Account for existing users. But I have some difficulties with new user registration using Google Oauth 2. For example: i've got google account "example#google.com". It's logged in on my current PC. And when I try to sign up with this account to my app it generates blank register form. If I dont manually provide email, login, full name, etc. - I've got error message that they "cannot be blank". I guess solution is create default value to text fields to fetch user details.
So, my question is how can I provide values for variables in view that equals variables from google account?
Email field in form_for in new user registration:
= f.email_field :email, :autofocus => true, :value => 'how can i put auth.info.email here?'
omniauth_callbacks_controller.rb:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
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
end
end
omniauth method from user model:
def self.from_omniauth(auth)
if user = User.find_by_email(auth.info.email)
user.provider = auth.provider
user.uid = auth.uid
user
else
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.full_name = auth.info.name
user.email = auth.info.email # THIS (user.email) value i want to provide to my registration form as default value
user.birthday = auth.info.birthday
user.avatar = auth.info.image
end
end
end
I had the same problem with GitHub you can take a look at my user model
https://github.com/flower-pot/pastebin/blob/master/app/models/user.rb
Related
The default devise authorization path (i.e. user_omniauth_authorize_path mentioned here) appears to be designed to work for both OmniAuth registration and login. By the time auth is received in the OmniauthCallbacksController, your rails app typically uses the auth information to create a new user or update an existing user regardless of whether the user intended to login or register with a service (i.e. Facebook).
My stakeholders have requested different behavior for auth and login. If a user clicks "Register with Facebook" and there's no account with their Facebook email, they want to go ahead and create an account with that email. However, if a user clicks "Login with Facebook" and there's no account with their Facebook email, they want to present the user with an error message explaining "The Facebook account you are using does not match an account in our records. Please sign in using your barre3 email and password"
The place for this logic seems to be in the OmniauthCallbacksController's Facebook method. What's the cleanest way to pass the user's intent ('login' or 'register') into this callback method?
You can get back parameters from the authorization to indicate the user's intent by adding some parameters on the authorization URL.
For example:
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider, {intent: :sign_in})
(This creates a URL like: http://whatevs.dev/users/auth/facebook?intent=sign_in)
Then, in the callbacks controller (whatever you name it):
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
if env["omniauth.params"]["intent"] == "sign_in"
# Don't create a new identity
else
# Normal flow, such as:
#user = User.from_omniauth(request.env["omniauth.auth"])
sign_in_and_redirect #user
end
end
end
If a user clicks "Register with Facebook" and there's no account with their Facebook email, they want to go ahead and create an account with that email
This assumption is not valid since Facebook can be created with just a phone number. Even user has an email, extra permission is required to get the user email from Facebook.
Your application should validate facebook_uid returned by Facebook API instead of email.
What's the cleanest way to pass the user's intent ('login' or 'register') into this callback method?
For OmniAuth there is no difference between the 'login' or 'register'. All it does is try to authenticate the user with the provided Facebook token.
One clean way to differentiate is to separate on the controller level. If user tries to login in, call SessionsController#create, if user tries to sign up, call UsersController#create.
I have also researched too much for the answer but didn't get any satisfactory answer so I took a step forward and create functionality to work as required. #Jason answer helps me to reach here.
views/users/sessions/new.html.erb
<% if devise_mapping.omniauthable? %>
<% resource_class.omniauth_providers.each do |provider| %>
<%= link_to omniauth_authorize_path(resource_name, provider, {intent: :sign_in}) do %>
<i class="fa fa-<%= provider.to_s.split('_')[0] %>"></i>
<% end %>
<% end %>
<% end %>
views/users/registrations/new.html.erb
<% if devise_mapping.omniauthable? %>
<% resource_class.omniauth_providers.each do |provider| %>
<%= link_to omniauth_authorize_path(resource_name, provider, {intent: :sign_up}) do %>
<i class="fa fa-<%= provider.to_s.split('_')[0] %>"></i>
<% end %>
<% end %>
<% end %>
controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
if request.env["omniauth.params"]["intent"] == "sign_up"
#user = User.from_omniauth_sign_up(request.env['omniauth.auth'])
elsif request.env["omniauth.params"]["intent"] == "sign_in"
#user = User.from_omniauth_sign_in(request.env['omniauth.auth'])
end
if #user.present?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
flash[:errors] = {:email => "is not registered with us."}
session["devise.#{provider}_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:google_oauth2, :facebook, :linkedin].each do |provider|
provides_callback_for provider
end
end
models/user.rb
def self.from_omniauth_sign_up(auth)
# check if email address obtained from auth server is in User table
user = User.where(email: auth[:info][:email]).first
if user.present?
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.image = auth.info.image
user.provider = auth.provider
user.uid = auth.uid
user.save
else
user = User.new(provider: auth.provider, uid: auth.uid)
user.email = auth.info.email
user.username = "#{auth.uid}-#{auth.provider}"
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.image = auth.info.image
user.status_id = 2
user.skip_mobile_validation = true
user.skip_confirmation!
user.save
end
user
end
def self.from_omniauth_sign_in(auth)
if user = User.where(email: auth[:info][:email]).first
user
end
end
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.
I'm trying to pass the username that is collected from Twitter or facebook when my users get authenticated and created, however a simple (description = user.username) doesn't seem to do it, here's what I've got so far;
def self.create_from_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.username = auth["info"]["nickname"]
#user.email = auth["info"]["email"]
user.save!
if user.save
Stripe::Customer.create(description = user.username)
end
user
end
end
I keep getting the following error, no matter if I use #user.username or even current_user.username.
undefined method `each' for "xhtmlit":String
I was completely doing it wrong which is why it was spitting out the error for me. The actual code might not be clean but I'm currently working to try and figure out from starting with dirty version to clean version. Here's what I've got so far that works;
if user.save
Stripe::Customer.create(:email => user.username)
end
I was following this tutorial on Omniauth: http://railscasts.com/episodes/235-omniauth-part-1?view=asciicast
I keep getting this error:
no such column: authentication.provider:
Now the main thing I want to know is why "provider" isn't being accepted. It exists in the class... the authentications database exists... so why is it saying it isn't there?
Here's my authentications controller:
class AuthenticationsController < InheritedResources::Base
def index
#authentications = current_user.authentications if current_user
end
def create
#user = User.where(authentication: auth).first_or_create(:provider => auth['provider'], :uid => auth['uid'])
self.current_user = #user
# auth = request.env["omniauth.auth"] current_user.authentications.create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
def auth
request.env['omniauth.auth']
end
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
flash[:notice] = "Successfully destroyed authentication."
redirect_to authentications_url
end
end
I can assure you I have a model called authentication and that this model has a provider and uid field. I've also tried where(authentications: auth) and where(auth: auth)
each with no luck.
Any ideas would be appreciated.
UPDATE
authentication.rb (model)
class Authentication < ActiveRecord::Base
attr_accessible :create, :destroy, :index, :provider, :uid, :user_id
belongs_to :user
end
UPDATE 2
I'm basically attempting to adapt this tutorial to rails 3.2.
The original line from the tutorial is commented out above.
UPDATE 3
Here is the entire first line of error:
SQLite3::SQLException: no such column: authentication.provider: SELECT "users".* FROM "users" WHERE "authentication"."provider" = 'facebook' AND "authentication"."uid" = '2222222' AND "authentication"."info" = '--- !ruby/hash:OmniAuth::AuthHash::InfoHash
Hate to be a burden... but the clock's really ticking, my ass is on the line, and I'm about to go completely insane trying to figure this out. If you can tell me just why provider isn't being accepted I'm sure I can figure out the rest.
your create action has not sense
User.where(authentication: auth) converts to SELECT * FROM users WHERE authentication = a_hash
You shoul do something like
auth1 = Authentication.where(provider: auth['provider'], uid: auth['uid']).first
if !auth1.nil?
current_user = auth.user
else
user = User.new
user.authentications.build(provider: auth['provider'], uid: auth['uid'])
user.save!
current_user = user
end
Since you are just adding a record in the authentications table, I am unable to understand why you are reassigning this.current_user. Also is current_user a helper method or a member, if it's a member where is it declared?
Don't you just want to create an authentication for the current user as such?:
def create
current_user.authentications.first_or_create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
This finds the first authentication record by provider and uid, if not found then creates that authentication record.
Also by that error, I hope you have figured out the answer to this question:
Now the main thing I want to know is why "provider" isn't being
accepted. It exists in the class... the authentications database
exists... so why is it saying it isn't there?
It is because you are calling first_or_create() on User object, not Authentication.
I also faced this issue recently. At first I thought I had forgotten to add a provider column to users table, but that wasn't it.
This is how I eventually solved it:
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.logo = auth["info"]["image"]
# if you use confirmable, since facebook validates emails
# skip confirmation emails
user.skip_confirmation!
end
end
auth is a hash like the one below, so instead of auth.provider, I used auth["provider"] etc:
omniauth.auth: {"provider"=>"facebook", "uid"=>"11111111111111", "info"=>{"email"=>"some#email.com", "image"=>"http://graph.facebook.com/v2.6/11111111111111/picture"}, "credentials"=>{"token"=>"sometoken", "expires_at"=>1506680013, "expires"=>true}, "extra"=>{"raw_info"=>{"email"=>"some#email.com", "id"=>"11111111111111"}}}
I'm working with the Omniauth Facebook Gem with Rails and need a profile pick on my app for each user.
Is it as simple as retrieveing the Name of the user from facebook with the Gem, or does it need to be uploaded to Amazon S3 servers etc.?
My user model:
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
Can't find a clear answer.
Updated:
In my show page it only shows the url:
<b>IMAGE:</b> <%= #user.image %>
Thanks
It is simple to do. And you don't require to store image on your server. You can simply fetch it from facebook.
Here is how I do it:
Add an image field in your User model:
rails generate migration addImageToUsers image:string
Add it attr_accessible list.
Then in your above method, add following listing for image:
user.image = auth.info.image
This is the direct url of where facebook stores the image of the user.
You can inspect your auth hash to study different size of user image.