I want to implement login and register with Twitter omniauth on my site. I have followd this Railscast, but when I click the "Log in with twitter" link, I receive a 401 Unauthorized. When I look in the log I see this:
GET "/users/auth/twitter/callback?oauth_token=xxx&oauth_verifier=xxx
omniauth: (twitter) Callback phase initiated.
Processing by OmniauthCallbacksController#twitter as HTML
Parameters: {"oauth_token"=>"xxx", "oauth_verifier"=>"xxx"}
User Load (1.3ms) SELECT "users".* FROM "users" WHERE "users"."provider" = 'twitter' AND "users"."uid" = '9999' ORDER BY "users"."id" ASC LIMIT 1
(0.3ms) BEGIN
(0.2ms) COMMIT
Completed 401 Unauthorized in 13ms
When I look in the omniauth controller, it has found a user, but it seems to fail on the sign_in_and_redirect user line.
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
# it reaches this
flash.notice = I18n.t('.devise.omniauth_callbacks.success')
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url, notice: I18n.t('.devise.omniauth_callbacks.failure')
end
end
alias_method :twitter, :all
end
In config/devise.rb I have added the key and secret like this:
config.omniauth :twitter, ENV.fetch("TWITTER_CONSUMER_KEY"), ENV.fetch("TWITTER_CONSUMER_SECRET")
And then the actual values are stored in .rvmrc like this:
export TWITTER_CONSUMER_KEY=xxx
export TWITTER_CONSUMER_SECRET=xxx
This probably means that it tries to login the found user but fails on some validation right? But I removed all validations in the user model to check what happens, and I still get this error. Anyone any idea what might be wrong here?
I had the same issue ("Omniauth returns a 401 unauthorized") but the cause was different.
In my User#from_omniauth I do this
where(provider: auth.provider, uid: auth.uid).first_or_create
which means that it creates a new user if one was not already there. Also, my User is devise :confirmable, and unconfirmed User can not sign in. That is why the authentication failed.
There would have been an error message stating this, but in the view that I was redirected to there was no:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
So, the solution was to add this to the method in the CallbacksController:
def github
#user = User.from_omniauth(request.env['omniauth.auth'])
if #user.persisted?
#user.skip_confirmation!
#user.save
#...
skip_confirmation is a devise method and it does this on the User:
self.confirmed_at = Time.now.utc
so, alternatively this could have been done in User.from_omniauth
The issue should be strong parameters. I would suggest you get all you want from request.env["omniauth.auth"] into some variables instead of directly using in for creation or find.
def self.from_omniauth(auth)
uid = auth[:uid]
provider = auth[:provider]
User.where(provider: provider, uid: uid).first
end
This should fix the unauthorized error.
Related
I have scoured these pages for weeks looking for an answer. I am a newbie to Ruby, i followed a tutorial and was able to successfully complete it with little fuss. My issue is when i decided to add more functionality to my app by authenticating users with Facebook using the Omniauth gem in addition to the devise gem that is working perfectly.
I almost know the solution will be simple to the trained eye but i am at a loss since i have tried numerous suggestions on this site and others with varying degrees of success.
My current problem is whenever a user tries to sign in using Facebook, user gets authenticated but is redirected to the signup page. I fiddled around sometime last week and was able to successfully login but just once and kept getting redirected to signup page subsequently.
My required scenario is thus:
If a user clicks on the sign in with Facebook link, they should get redirected to Facebook for authentication, then sent back to my Ruby application and the values for email, first_name, last_name should get added to the User table for that user.
For a returning User,
All database values should be checked and user is logged in automatically without much fuss.
I would also like an email unique constraint to ensure we do not have multiple people with the same email.
I would really appreciate some sort of direction as to where i am getting it wrong.. Like i mentioned above, i am a total greenhorn in this space as i work mainly with IT infrastructure.
Please see my code below:
callbacks_controller.rb
class CallbacksController < 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"])
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
def failure
redirect_to root_path
end
def failure
redirect_to root_path
end
end
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
has_many :reviews, dependent: :destroy
#validates :first_name, :last_name, presence: true
devise :omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(email: auth.info.email).first_or_initialize.tap do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[6,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.save
end
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]
["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
user.first_name = data["first_name"] if user.first_name.blank?
user.last_name = data["last_name"] if user.last_name.blank?
end
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks"}
resources :hospitals do
collection do
get 'search'
end
resources :reviews, except: [:show, :index]
end
get 'pages/Hospitals'
get 'pages/Labs'
get 'pages/Doctors'
get 'pages/about'
get 'pages/contact'
root'hospitals#index'
# For details on the DSL available within this file, see
http://guides.rubyonrails.org/routing.html
end
Console Response
Started GET "/users/auth/facebook" for ::1 at 2017-06-13 14:02:29 +0100
I, [2017-06-13T14:02:29.142018 #8385] INFO -- omniauth: (facebook) Request
phase initiated.
Started GET "/users/auth/facebook" for ::1 at 2017-06-13 14:02:29 +0100
I, [2017-06-13T14:02:29.488425 #8385] INFO -- omniauth: (facebook) Request
phase initiated.
Started GET "/users/auth/facebook/callback
code=AQAJ33qxsDJhSh2fKc8YH9YANZwK2BagO3fotR22iw3
cOeTN5G2HSvXbOioiwaQmwrZB3EEZKZBWlBAK4c
RVyddoG8oaeLQfEXjA0FPOvZtpw0XiuBGwOJIh7YaDSjt7O33Dn2mB7Vlu2YUaT-
DxlY3ioOVhNx8ymCE6TMGJx0slL-NvMB8b52IHSheMvPYTcMAoj2WXPgrLK8aH0eox_
7VbD8zaV0QFeJxqask3gaU4GTkGI50liO2SdF
T9fyFVWTgfORNP0yhwoH3HNlMGIznqSqbRGB43d
2qULNHglH6exDMCzgpyhD3Bmi2lxzcLc10"
for ::1 at 2017-06-13 14:02:29 +0100
I, [2017-06-13T14:02:29.731093 #8385] INFO -- omniauth: (facebook) Callback
phase initiated.
Processing by CallbacksController#facebook as HTML
Parameters:
{"code"=>"AQAJ33qxsDJhSh2fKc8YH9YANZwK2BagO3
fotR22iw3cOeTN5G2HSvXbOioiwaQmwrZB3EEZK
ZBWlBAK4cRVyddoG8oaeLQfEXjA0FPOvZtpw0XiuBGwOJIh7YaDSjt7O33Dn2mB7Vlu2YUaT-
DxlY3ioOVhNx8ymCE6TMGJx0slL-
NvMB8b52IHSheMvPYTcMAoj2WXPgrLK8aH0eox_
7VbD8zaV0QFeJxqask3gaU4GTkGI50liO2SdFT9fy
FVWTgfORNP0yhwoH3HNlMGIznqSqbRGB43d2qULNHglH6exDMCzgpyhD3Bmi2lxzcLc10"}
User Load (0.3ms) SELECT "users".*
FROM "users" WHERE "users"."email" IS NULL
ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.2ms) begin transaction
(0.1ms) rollback transaction
Redirected to http://localhost:3000/users/sign_up
Completed 302 Found in 265ms (ActiveRecord: 0.6ms)
New Console response
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."provider" = ?
AND
"users"."uid" = ? ORDER BY "users"."id" ASC LIMIT ? [["provider",
"facebook"], ["uid", "104903843446146"], ["LIMIT", 1]] (0.1ms) begin
transaction (0.1ms) rollback transaction Redirected to
localhost:3000/users/sign_up
Try below code:
def facebook
#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
end
end
model
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]
end
end
Hi I'm trying to create a oauth registration through omniauth, using buffer2. Here is what I have so far, the problem I am having now is that I get redirected to the new_user_registration_path.
I do get taken to buffers site, where I accept that the app receives rights.
omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def buffer
#user = User.find_for_buffer(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = "devise.omniauth_callbacks.success"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.buffer_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
and in the model:
def self.find_for_buffer(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:provider => access_token.provider, :uid => access_token.uid).first
if user
user
else
user = User.create(
email: data.email,
provider: access_token.provider,
uid: access_token.uid,
password:Devise.friendly_token[0,20]
)
end
user
end
the site returns me to the new_user_registration_url, and console is saying:
INFO -- omniauth: (buffer) Callback phase initiated.
Processing by Users::OmniauthCallbacksController#buffer as HTML
Parameters: {"state"=>"3ee6956fe2d74bf9a114a29cc55c9c70260aba7f0bd402d9", "code"=>"1/686ea44403ab403d0c6e647338a936f8"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."provider" = 'buffer' AND "users"."uid" = '526362667f9a1f3f5994515a' ORDER BY "users"."id" ASC LIMIT 1
(0.2ms) BEGIN
(0.2ms) ROLLBACK
Redirected to http://localhost:3000/users/sign_up
Completed 302 Found in 108ms (ActiveRecord: 1.0ms)
I've also attached the url for the api documentation they offer, as I'm a rookie it doesn't make all that sense to me, plus their documentation in my opinion is somewhat crappy as-well:
https://bufferapp.com/developers/api
By default with devise, you need to provide an email to your User.
You're requesting data.email but some OAuth providers don't have en email in their response, like Twitter. Your provider doesn't seem to provide an email when I look at the documentation.
Try to create a user manually providing an email and see what happen:
User.create(
email: "a-manual-entry#domain.com",
provider: "a-token",
uid: "1234567890",
password:Devise.friendly_token[0,20]
)
From here, you have multiple options, but you should begin here.
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
I'm having some trouble storing a user's Twitter access token in the User model. I have installed the Omniauth gem as per Railscast #241 and was successful in setting up the Twitter authentication, storing the "uid" and "name". In order to make authenticated Twitter API calls, I wanted to store the user's access token and access token secret and thus created a migration to create those fields. I did that successfully and can assign those fields successfully in the Rails Console to records that do not have the stored. When trying to authenticate a new user, however, and pull this information in from the start, I get the error listed in the title. HEre is additional error information:
app/models/user.rb:13:in `block in create_from_omniauth'
app/models/user.rb:10:in `create_from_omniauth'
app/models/user.rb:6:in `from_omniauth'
app/controllers/sessions_controller.rb:5:in `create'
I follow down this path from the sessions controller to the User model but can't figure out what is causing the error. I have included those documents below.
Sessions Controller
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url, notice: "Signed in"
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Signed out"
end
end
User Model
class User < ActiveRecord::Base
attr_accessible :name, :uid, :access_token, :access_token_secret
has_many :events
def self.from_omniauth(auth)
where(auth.slice('uid')).first || create_from_omniauth(auth)
end
def self.create_from_omniauth(auth)
create! do |user|
user.uid = auth["uid"]
user.name = auth["info"]["nickname"]
user.access_token = auth["extra"]["access_token"]["token"]
user.access_token_secret = auth["extra"]["access_token"]["secret"]
end
end
end
Can anyone help me troubleshoot this error? I know it has to do with setting the access_token fields in the create_from_omniauth method as it works fine without them. I've been banging my head trying to figure out why these won't work. Thanks in advance for any help.
I doubt why you need to save these tokens as they looks of no use. In this case, the most important thing is the Twitter returned uid for identifying or create user.
Anyway, the reason of your error is there are not such keys.
auth['extra']['access_token'] is a string, there is no further sub keys.
:extra => {
:access_token => "", # An OAuth::AccessToken object
To access token and secret, you can use auth['credentials']
:credentials => {
:token => "a1b2c3d4...", # The OAuth 2.0 access token
:secret => "abcdef1234"
},
Reference:
https://github.com/arunagw/omniauth-twitter#authentication-hash
https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later
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"}}}