I know this has been asked many times, but the answers are never fully acceptable to me.
So I am following Ryan Bates' Railscast about this topic, and mixing that with the official Devise Omniauth guide (that is based on FB), but I am just not getting it to work like I expect, so I would love some help.
I have a Users::OmniauthCallbacksController that looks like this:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect root_path, :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"].except("extra")
flash[:notice] = flash[:notice].to_a.concat resource.errors.full_messages
redirect_to new_user_registration_url
end
end
alias_method :twitter, :all
def failure
redirect_to root_path
end
end
Then I also have two methods on my User.rb
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.update(
email: auth.info.email,
password: Devise.friendly_token[0,20],
username: auth.info.nickname,
remote_avatar_url: auth.info.image,
token: auth.credentials.token,
secret: auth.credentials.secret
)
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.twitter_data"]
# user.attributes = params
user.update(
email: params[:email],
password: Devise.friendly_token[0,20],
username: data["info"]["nickname"],
remote_avatar_url: data["info"]["image"],
token: data["credentials"]["token"],
secret: data["credentials"]["secret"]
)
end
end
end
I run into a variety of problems. The most immediate is because I am setting the password, the user doesn't know the password when they try to login (and I don't auto sign them in upon confirmation).
But if I don't set the password, it doesn't ask them to set the password...so that's kinda weird too.
These are my devise settings on my User model:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :omniauthable, :omniauth_providers => [:twitter]
validates :username,
presence: true,
uniqueness: {
case_sensitive: false
}
validate :validate_username
def validate_username
if User.where(email: username).exists?
errors.add(:username, :invalid)
end
end
So my question is this, when someone signs up via Twitter, do they need to enter a password? I automatically send them to the registration/new.html.erb anyway because Twitter doesn't return an email value. But I am trying to just get the process working first, before optimizing it.
How do I deal with the password issue?
Edit 1
For more clarity, I will have to deal with this password_required issue regardless of the OAuth provider.
So how do I override that requirement for all OAuth providers?
You should add the following method to the User class:
def password_required?
(provider.blank? || uid.blank?) && super
end
Since Twitter doesn't return the user's email, you may also want to tweak that email validation, but redirecting the user to registration/new.html.erb like you are already doing seems like the correct approach to me.
Related
I'm trying use the google login oauth2 in my project, all the codes successfully fixed in right position but I dont want the google oauth2 features to request for email verification just straight to login, I prefer it to get skipped, how do I achieve this without removing the :confirmable devise module? this is my code user.rb in model
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, :omniauthable, omniauth_providers: [:github, :google_oauth2]
has_many :friends
def self.from_omniauth(access_token)
data = access_token.info
user = User.where(email: data['email']).first
unless user
user = User.create(
email: data['email'],
password: Devise.friendly_token[0,20]
)
end
user
end
end
This is code for the omniauth callback controller .rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
handle_auth "Google"
end
def github
handle_auth "Github"
end
def handle_auth(kind)
# 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?
flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: kind
sign_in_and_redirect #user, event: :authentication
else
session['devise.auth_data'] = request.env['omniauth.auth'].except('extra') # Removing extra as it can overflow some session stores
redirect_to new_user_registration_url, alert: #user.errors.full_messages.join("\n")
end
end
end
The Devise Confirmable Module provides a skip_confirmation! method to do this. In your from_omniauth function, instead of
user = User.create(
email: data['email'],
password: Devise.friendly_token[0,20]
)
Instead do this
user = User.new(
email: data['email'],
password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!
I followed this tutorial to add user authentication to my rails app. The tutorial uses devise and google_oauth2.
When a user attempts to sign in with Google, the user encounters an ArgumentError after callback.
Error:
Something is broken between the user model and the callback controller, but I'm a bit too over my skis to understand what might be causing the problem.
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
devise :omniauthable, omniauth_providers: [:google_oauth2]
def self.from_google(email:, full_name:, uid:, avatar_url:)
create_with(uid: uid, full_name: full_name, avatar_url: avatar_url).find_or_create_by!(email: email)
end
end
omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
user = User.from_google(from_google_params)
if user.present?
sign_out_all_scopes
flash[:success] = t 'devise.omniauth_callbacks.success', kind: 'Google'
sign_in_and_redirect user, event: :authentication
else
flash[:alert] = t 'devise.omniauth_callbacks.failure', kind: 'Google', reason: "#{auth.info.email} is not authorized."
redirect_to new_user_session_path
end
end
protected
def after_omniauth_failure_path_for(_scope)
new_user_session_path
end
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || root_path
end
private
def from_google_params
#from_google_params ||= {
uid: auth.uid,
email: auth.info.email,
full_name: auth.info.name,
avatar_url: auth.info.image
}
end
def auth
#auth ||= request.env['omniauth.auth']
end
end
I am guessing (!!) that you are running the latest version of ruby: 3.0.0, which was released in December 2020, after that tutorial was written.
There is a major, breaking change in ruby version 3: Keyword and positional arguments are now separated.
The error is very subtle, but it's happening because your code is doing this:
User.from_google(
{
uid: auth.uid,
email: auth.info.email,
full_name: auth.info.name,
avatar_url: auth.info.image
}
)
(i.e. Passing a Hash to the method), instead of this:
User.from_google(
uid: auth.uid,
email: auth.info.email,
full_name: auth.info.name,
avatar_url: auth.info.image
)
(i.e. Passing keyword arguments to the method.)
Fixing this problem is easy: You can use the "double-splat" operator (**) to convert a hash into keyword arguments:
user = User.from_google(**from_google_params)
If you are not comfortable making such corrections to the code when following a tutorial like this, it should also be perfectly fine to use an older ruby version (e.g. 2.7). The "fixed" version of the code I have shown above will work fine on all ruby versions.
I have configured Facebook via login using this tutorial and it's working fine. I'm able to send a login call at Facebook and after users acknowledgment, it's redirect to my site. However, In the callback URL, only "code" is returning but I required an access token.
Here is my code.
Devise.rb
config.omniauth :facebook, "client_id", "secret", scope: 'public_profile,email,manage_pages,read_insights',:display => 'popup'
User.rb
devise :database_authenticatable,:registerable,:recoverable,
:rememberable, :trackable, :validatable,
:confirmable,:lockable,:timeoutable,
:omniauthable, omniauth_providers: [:facebook]
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
def self.create_from_provider_data(provider_data)
where(provider: provider_data.provider, uid: provider_data.uid).first_or_create do | user |
user.email = provider_data.info.email
user.password = Devise.friendly_token[0, 20]
user.skip_confirmation!
end
end
omniauth controller
def facebook
#user = User.create_from_provider_data(request.env['omniauth.auth'])
if #user.persisted?
sign_in_and_redirect #user
set_flash_message(:notice, :success, kind: 'Facebook') if is_navigational_format?
else
user = User.create!(email: auth.info.email,
password: Devise.friendly_token[0,20],
user_name: auth.info.first_name
)
user.authentications.create!(user_id: user.id,
provider: auth.provider,
uid: auth.uid)
flash[:error] = 'There was a problem signing you in through Facebook. Please register or try signing in later.'
redirect_to new_user_registration_url
end
end
def failure
flash[:error] = 'There was a problem signing you in. Please register or try signing in later.'
redirect_to new_user_registration_url
end
CallBack Response:
Parameters: {"code"=>"AQBBtixd3nmIkT_KyKUKmy68hBYt7kdZ0jX1pATNmDCSWmjfoiAC_8C5aOF3P3jKRyhKYX2JfL0gZDWZCTraN_kdZpxIJu8pYWkktr0E9Q2WXk6xjz0Uyz4cS2YeTv0SyqjZbZvQr8roxKCPvZb-6UKbCzMGZsi3-VLXK3suPfs729nR8MilmuatZqQ-TanApTy-8Qh195ntqfD6gIMhbZZMBhAuVSbf6GmyT-anNV0exczDMzKIF6OpZoIFC7Vxez8EH3cR7BUPqc5OldfbDD8j9pY6kDeoc00An9wuGj4hAfne_jeShUZTw_zSQMmxkbMqe2acFPJWE5DLL2QNII54", "state"=>"c231b221dba5bdc33fff6349af5793c0cbb5bdcc69253372"}
As per this documentation, we can get required information from callback response.
How i can update my code to get access token.
You can to send another request with the code to get the access token.
You can fetch email and other details by request.env['omniauth.auth']['info']['email'].
As I can see you are using auth.info.email and auth is not defined due to which error persist.
I used the Devise gem to set up a User model for my app. I'm trying to work in Facebook authentication using Omniauth. I can retrieve the Name and Email data, but I'm having trouble getting any other public_profile data. In this example I'm trying to get Gender, but none of the other data works either. When I visit this path: user_omniauth_authorize_path(:facebook), the "facebook" action in "controllers/registrations_controller.rb" is called. But for the user that is created, user.gender, and all other data other than name and email, comes back nil.
config/initializers/devise.rb
config.omniauth :facebook, "<ID>", "<SECRET>", scope: 'email', display: 'popup', info_fields: 'email,name,gender'
app/models/devise.rb
devise :omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.gender = auth.extra.raw_info.gender.to_s
end
end
app/controllers/registrations_controller.rb
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?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
gemfile
gem 'devise'
gem 'omniauth-facebook'
What's strange is I can't even get the class type of the data. Name and Email return "String" if I call class.to_s on them, but all the other data returns "NilClass".
What am I doing wrong?
UPDATE:
Okay I literally have no idea what could have possibly changed, but this same exact code now suddenly works....so problem solved I guess?
Try This way
def self.from_omniauth(access_token)
data = access_token.info
email = data.info.email
first_name = data.first_name
last_name = data.last_name
end
You can gender attribute using auth.info.gender
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.gender = auth.info.gender
end
You sure the user has these data as public info?
I would try to debug the permissions in Facebook's open graph explorer
https://developers.facebook.com/tools/explorer/
That will help you understand wether it is permissions, lacking data issue or a problem with the integration.
I'm playing around with the omniauth-facebook gem to log into a devise session through a facebook account. When I click the "Sign in with facebook" link, everything goes well: a new account is created, I'm signed in and bounce back to the homepage with a message confirming my new session (very good!).
Problem: However when an account already exists, upon clicking the link I am redirected to the user/sign_up page. I've been following this documentation from the Devise wiki. There is a good deal of documentation on similar errors here, here, here and here. Each of the solutions, however, are already implemented in my app (as far as I can tell) OR (in the case of the last link) seem to be based on an older configuration model that seems sufficiently different from the wiki that I'm not sure it's applicable.
My best guess is that it has something to do with the callbacks controller, as #user.persisted? seems to be coming up false.This leads me to believe that my definition of #user is not correct. See below:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
logger.debug "Inside 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"])
logger.debug "User is #{#user}"
if #user.persisted?
logger.debug "#user.persisted?"
debugger
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?
logger.debug "user exists"
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path, alert: "Login failed"
end
end
Additionally, my user model is as follows:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = Devise.friendly_token[0,20]
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.fname = auth.info.first_name
user.lname = auth.info.last_name
end
end
end
Any suggestions would be certainly welcome! Thanks in advance.
Try Something Like This
class Authentication < ActiveRecord::Base
belongs_to :user
# validates :provider, :uid, :presence => true
def self.from_omniauth(auth)
authenticate = where(provider: auth[:provider], :uid=>auth[:uid]).first_or_initialize
if authenticate.user
authenticate.provider = auth[:provider]
authenticate.uid =auth[:uid]
else
user = User.find_or_initialize_by(:email => email)
authenticate.provider = auth[:provider]
user.email = email
user.first_name = first_name
user.last_name = last_name
user.social_image = image
user.password = Devise.friendly_token.first(8)
user.save(validate: false)
if user.errors.any?
return user
else
authenticate.user_id = user.id
end
end
authenticate.save
authenticate.user
end
end
Try this
def after_sign_in_path_for(resource)
super resource
end
From what i perceived that you are not going to your landing page
from_omniauth never finds an existing facebook user because you are overwriting the provider attribute:
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
searches for a user with provider 'facebook' in this case, but none can be found:
user.provider = Devise.friendly_token[0,20]
changes the provider to some random token
just remove that line and it should work properly