I'm using omniauth-facebook and devise for authentication in my rails 4 app. I would like for a user, with multiple emails, that already has authenticated through devise, also be able to add facebook auth later if they choose.
Device settings:
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable,
:lockable, :timeoutable, :omniauthable, omniauth_providers: [:facebook]
user.rb
def self.from_omniauth auth
email = Email
.includes(:user)
.where(email: auth.info.email)
.first_or_initialize
ui = Identity
.where(provider: auth.provider, uid: auth.uid)
.first_or_initialize
if ui.persisted?
# Existing user, Existing social identity
if ! email.persisted?
# Email changed on third party site
email.user = ui.user
email.save!
ui.email = email
elsif email.user == ui.user
ui.user
else
raise Exceptions::EmailConflict.new
end
elsif email.persisted?
# Existing User, new identity
ui.user = email.user
ui.save!
ui.user
else
# New user new identity
email.save!
user = User.new(
password: Devise.friendly_token[0,20],
default_email: email
)
user.save!
ui.user = user
ui.email = email
ui.save!
end
ui.user
end
private
def save_default_email
if default_email.user.blank?
default_email.user = self
elsif default_email.user != self
raise Exceptions::EmailConflict
end
default_email.save!
end
Logs:
ActiveRecord::RecordInvalid (Validation failed: Email has already been taken):
app/models/user.rb:98:in `save_default_email'
app/models/user.rb:82:in `from_omniauth'
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 am using RoR 5.2, devise and omniauth-trello gem.
I can't sign in with Trello.
I need to create ability to sign in without existing user with returned provider and uid: it should create user if it doesn't exist.
I have already added in config/routes.rb:
devise_for :users, controllers: { omniauth_callbacks: 'omniauth_callbacks' }
config/initializers/devise.rb:
config.omniauth :trello, "#{Rails.application.credentials.trello[:key]}", "#{Rails.application.credentials.trello[:secret]}"
config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :trello, Rails.application.credentials.trello[:key], Rails.application.credentials.trello[:secret],
app_name: "Trello-Rooney", scope: 'read,write,account', expiration: 'never'
end
app/controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def trello
#user = User.from_omniauth(request.env['omniauth.auth'])
if #user.persisted?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: 'Trello') if is_navigational_format?
end
end
end
app/models/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, :omniauthable, omniauth_providers: %i[trello]
def self.from_omniauth(auth)
user = User.where(provider: auth.provider, uid: auth.uid.to_s).first
return user if user
email = auth.info[:email]
while email.nil?
generated_email = "#{SecureRandom.base58(10)}#roonyx.trello"
if User.where(email: generated_email).blank?
email = generated_email
end
end
user = User.where(email: email).first
if user
user.update(provider: auth.provider, uid: auth.uid)
else
password = Devise.friendly_token[0, 12]
user = User.create!(email: email, password: password, password_confirmation: password, provider: auth.provider, uid: auth.uid)
end
user
end
end
But when I am trying to sign in with Trello, I see this in console. Looks like it doesn't cause my callback. Can anyone help? Thank you in advance.
E, [2019-05-13T14:10:29.647241 #19958] ERROR -- omniauth: (trello) Authentication failure! service_unavailable: Net::HTTPFatalError, 500 "Internal Server Error"
[2019-05-13 14:10:29] (pida=19958) INFO -- : Processing by OmniauthCallbacksController#failure as HTML
[2019-05-13 14:10:29] (pida=19958) INFO -- : Parameters: {"oauth_token"=>"47215df2b25b4fc089953da32acf0730", "oauth_verifier"=>"8a0b310f2afe98d0aebbd2073efc5b54"}
[2019-05-13 14:10:29] (pida=19958) INFO -- : Redirected to http://localhost:3000/users/sign_in
[2019-05-13 14:10:29] (pida=19958) INFO -- : Completed 302 Found in 1ms (ActiveRecord: 0.0ms)
The error is right there in your log:
Authentication failure! service_unavailable: Net::HTTPFatalError, 500 "Internal Server Error"
Your callback isn't being hit because you've inserted the OmniAuth::Builder middleware. You could try removing that and inspecting the params that are sent along with the callback.
Once you're removed the middleware you can drop a byebug or a binding.pry at the top of your callback action. Once you're in the debugger check the value of request.env['omniauth.auth']. That should give you some insight as to what the problem is. Hard to say more without knowing more about the environment.
I started getting this error after updating my rails app and all the gems to the most current versions and cant figure out whats causing it:
ERROR -- omniauth: (google_oauth2) Authentication failure! csrf_detected: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detected
First heres the initializer in devise.rb:
config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'],
{ access_type: 'offline',
prompt: 'consent',
select_account: true,
scope: 'userinfo.email,userinfo.profile',
client_options: {ssl: {ca_file: Rails.root.join("cacert.pem").to_s}}
}
User.rb has this:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable, omniauth_providers: [:google_oauth2, :facebook]
def self.from_omniauth(auth)
identity = Identity.find_for_oauth(auth)
if identity.nil?
identity = Identity.create_with_oauth(auth)
end
if identity.user.present?
return identity.user
else
registered_user = User.find_or_create_by(email: auth[:info][:email])
if registered_user && auth[:provider] == "google_oauth2"
registered_user.firstname = auth[:info][:first_name] if registered_user.firstname.blank?
registered_user.lastname = auth[:info][:last_name] if registered_user.lastname.blank?
registered_user.displayname = auth[:info][:name] if registered_user.displayname.blank?
registered_user.avatar_remote_url = auth[:info][:image] if registered_user.avatar_data.blank?
identity.user = registered_user
identity.save
registered_user.skip_confirmation!
registered_user.avatar_remote_url = auth[:info][:image]
registered_user.save
return registered_user
end
end
end
The Callback controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# You should configure your model like this:
# devise :omniauthable, omniauth_providers: [:twitter]
# You should also create an action method in this controller like this:
def google_oauth2
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user
sign_in #user
redirect_to root_path
else
redirect_to new_user_session_path, notice: 'Access Denied.'
end
end
Ive tried searching for an answer but nothing fits the same and has worked
I followed Railscasts(ASCII versions) #235 and and part of #236 to setup creating user authentications using OmniAuth & Devise: OmniAuth Part 1 OmniAuth Part 2
I am at the stage where I just modified the create method of the authentications controller to allow user's not signed in to the site to sign in directly via twitter. The code for the create method is as follows:
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."
logger.info("AUTHENTICATION: #{authentication.inspect}")
#logger.info("AUTHENTICATION METHODS: #{authentication.methods.sort}")
logger.info("authentication.user: #{authentication.user}")
#logger.info("authentication.user.nil?: #{authentication.user.nil?}")
#logger.info("authentication.user.id: #{authentication.user.id}")
sign_in_and_redirect(:user, authentication.user)
else
current_user.authentications.create(:provider => omniauth['provider'], :uid => omniauth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
end
end
Now when I go to /auth/twitter, I get this error:
No route matches "/auth/failure"
This is because authentication.user is nil. The code for the create method is exactly as per the Railscast, and I don't see why authentication.user is nil.
This is the output of the authentication.inspect:
#<Authentication id: 1, user_id: 1, provider: "twitter", uid: "319521616", created_at: "2011-08-01 10:32:48", updated_at: "2011-08-01 10:32:48">
Does anyone have any insight as to whyauthentication.user would be nil, even tough the inspect method returns valid data.
Here is the code from my user model:
class User < ActiveRecord::Base
has_many :authentications
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:lockable, :confirmable #Added lockable and confirmable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
end
Do you have any validations in your user model? This caused a silent fail on saving the user model, for me, which led to the error message you describe, when I did the same set up. Just one idea.