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
Related
I have a Rails 5 site which consists of 2 parts:
Admin area
API-only client area
I'm using Devise for both parts and https://github.com/lynndylanhurley/devise_token_auth gem for the API frontend.
The problem is about using the omniauth authentication. When I omniauth authenticate into the admin area - everything is ok - I get back some successful HTML-response.
But the problem is that I'm getting the same HTML-response in the API-area - but I need some JSON-response - not HTML one.
Here is my code:
config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { sessions: 'users/sessions', :omniauth_callbacks => 'users/omniauth_callbacks' }
namespace :api do
mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'api/users/sessions', :omniauth_callbacks => 'api/users/omniauth_callbacks' }
end
end
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules.
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable,
:omniauth_providers => [:facebook, :vkontakte]
include DeviseTokenAuth::Concerns::User
devise :omniauthable
def self.from_omniauth_vkontakte(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.extra.raw_info.first_name.to_s + "." + auth.extra.raw_info.last_name.to_s + '#vk.com'
user.password = Devise.friendly_token[0,20]
end
end
end
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def vkontakte
#user = User.from_omniauth_vkontakte(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 => "Vkontakte") if is_navigational_format?
else
session["devise.vkontakte_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
config/initializers/devise.rb
Devise.setup do |config|
config.omniauth :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"], provider_ignores_state: true
config.omniauth :vkontakte, ENV["VKONTAKTE_APP_ID"], ENV["VKONTAKTE_APP_SECRET"]
end
Gemfile
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-vkontakte'
Gemfile.lock
devise (4.3.0)
devise_token_auth (0.1.42)
Here's my log:
Started GET "/api/auth/vkontakte" for 127.0.0.1 at 2017-06-20 17:34:23
+0300
Started GET "/omniauth/vkontakte?namespace_name=api&resource_class=User" for
127.0.0.1 at 2017-06-20 17:34:23 +0300
I, [2017-06-20T17:34:23.237270 #15747] INFO -- omniauth: (vkontakte) Request phase initiated.
Started GET "/omniauth/vkontakte/callback?code=0b8446c5fe6873bb12&state=52254649eb899e3b743779a1a4afc0304f249a6dd90b4415" for 127.0.0.1 at 2017-06-20 17:34:23 +0300
I, [2017-06-20T17:34:23.672200 #15747] INFO -- omniauth: (vkontakte) Callback phase initiated. Processing by Users::OmniauthCallbacksController#vkontakte as */* Parameters: {"code"=>"0b8446c5fe6873bb12", "state"=>"52254649eb899e3b743779a1a4afc0304f249a6dd90b4415"}
I guess that the problem is about a so-called "callback" url. I don't understand where it is set. It is obvious from the log that at the end of the auth process the GET "/omniauth/vkontakte/callback..." query is called. And probably it is called always - no matter if I initiated the oath sequence from admin or api client area.
I use Chrome Postman to make the API query http://localhost:3000/api/auth/vkontakte - and I get the HTML-response back ("successful login etc.") - but I need surely some JSON-response.
Is there a way to dynamically change the callback path depending on some precondition?
Is the callback query somewhat different depending on from where the oath procedure was initiated?
EDIT1:
This is not a single problem here unfortunately. Looks like the oauth is simply not implemented in the https://github.com/lynndylanhurley/devise_token_auth gem. So, even if I succeed to switch the oauth login procedure to the JSON way - how do I login the user the devise_token_auth-way - generating 3 tokens etc...? The app/controllers/users/omniauth_callbacks_controller.rb needs to be totally reimlemented.
You can render json from your OmniauthCallbacksController based on some extra parameter provided when your request a connection from the API for example.
These extra parameters will be availables in this hash request.env["omniauth.params"].
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def vkontakte
#user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])
if #user.persisted?
sign_in #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
if request.env["omniauth.params"]["apiRequest"]
render status: 200, json: { message: "Login success" }
else
redirect_to after_sign_in_path_for(#user)
end
else
session["devise.vkontakte_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
You can this extra parameters by calling the auth helper with additional parameters, they will be passed to your OmniauthController : user_vkontakte_omniauth_authorize_path(api_request: true) (Or whatever your route helper is)
I ended up implementing my own oauth callback procedure - instead of using one from the devise_token_auth gem.
The devise_token_auth gem does contain the oauth authentication - but it appears to be not working properly.
Here are my code changes:
config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { sessions: 'users/sessions', :omniauth_callbacks => 'users/omniauth_callbacks' }
namespace :api do
mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'api/users/sessions'}
end
end
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
include DeviseTokenAuth::Concerns::SetUserByToken
def vkontakte
#user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])
namespace_name = request.env["omniauth.params"]["namespace_name"]
if #user.persisted?
if namespace_name && namespace_name == "api"
#client_id = SecureRandom.urlsafe_base64(nil, false)
#token = SecureRandom.urlsafe_base64(nil, false)
#user.tokens[#client_id] = {
token: BCrypt::Password.create(#token),
expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
}
#user.save
#resource = #user # trade-off for "update_auth_header" defined in "DeviseTokenAuth::Concerns::SetUserByToken"
sign_in(:user, #user, store: false, bypass: false)
render json: #user
else
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
end
else
session["devise.vkontakte_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
The inclusion of include DeviseTokenAuth::Concerns::SetUserByToken provides 5 auth headers in response:
access-token →BeX35KJfYVheKifFdwMPag
client →96a_7jXewCThas3mpe-NhA
expiry →1499340863
token-type →Bearer
uid →376449571
But the response still lacks these headers (available at a common sign-in):
Access-Control-Allow-Credentials →true
Access-Control-Allow-Methods →GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Origin →chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo
Access-Control-Max-Age →1728000
I don't know whether they are important and if yes - how to provide them.
PS The same identical approach works with Facebook too.
I'm using the omniauth-facebook gem with devise. It was working until recently. I also recently upgrated to Rails 5.0.1 from Rails 4, but I'm not sure that's the cause.
I currently have 0 users, and I'm logged into Facebook. But when I try to sign up for my app with Facebook on localhost, I get this error:
NoMethodError in RegistrationsController#facebook
undefined method `provider' for nil:NilClass
Here is my User model. I marked the line that the error highlights.
User.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| #ERROR
#data = auth.info
user.name = #data.name
# ...
end
end
RegistrationsController
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
Also, here's my link:
<%= link_to "fb", user_facebook_omniauth_callback_path(:facebook, thing: #thing.id, degree: #degree, :format => :js) %>
The HTML Output:
<a href=\"/auth/facebook/callback.js?thing=2\">fb<\/a>
And the path:
localhost:3000/auth/facebook/callback.js?thing=2
So the problem is that request.env["omniauth.auth"] is nil for some reason. I can't find any traces of similar errors in any documentation.
Anyone encounter this before or have any thoughts?
To authenticate via facebook all you need is to put a link from your Site to facebook like this:
www.yoursite.com/auth/facebook
and then set up a route to receive the callback from Facebook with the authentication hash:
#routes.rb
get 'auth/facebook/callback' => 'sessions#create_facebook'
Can you specify how the output of this line looks like or why you are passing other information ?:
<%= link_to "fb", user_facebook_omniauth_callback_path(:facebook, thing: #thing.id, degree: #degree, :format => :js) %>
EDIT
auth/facebook/callback is a get request. Facebook sends you the users authentication hash there. Only facebook itself should use that route. When you want to authenticate your link has to be:
localhost:3000/auth/facebook
They way you have it, omniauth is expecting facebook's authentication hash but receives "?thing=2" which results in a failed authentication. Omniauth tries to extract the information from "?thing=2" which is not a hash and when you try to access auth[provider], auth is empty and therefore provider is not defined either which results in :
undefined method `provider' for nil:NilClass
I had the same issue and solved it by removing :omniauthable from User model
I've set up a rails app which uses devise as the authentication gem. However, I wanted the user to choose a random user name and not be tied down to sharing his/her email with me. I followed the instructions here. However, the issue is that whenever I try to signup using a username (with an empty user database) the form shows the error username is invalid. For reference here are the validations that I'm runnning:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :username, :uniqueness =>{
:case_sensitive => false
}, presence: true
I don't know how to get around this issue. Any help would be really nice.
Edit: In order to give a more complete picture of the situation here's the server log when signing up:
Started POST "/users" for ::1 at 2016-02-11 14:29:55 +0530
Processing by Devise::RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"mM1N0AgxfoqXz4448EgHoSYz03Hdr3nX/WH1f+siRP/26VNWz4VS5lJ2I4NJjXcucgWxFvgIyON+7zwFXAESgw==", "user"=>{"username"=>"ankit0912", "email"=>"", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up"}
(0.1ms) begin transaction
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."username") = LOWER('ankit0912') LIMIT 1
(0.1ms) rollback transaction
Thanks.
Edit2: Users_Controller
class UsersController < ApplicationController
def index
#users = User.order(:karma :desc).limit(25)
end
def show
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
#user = #user.update(user_params)
if #user.save
flash[:notice] = "Record Updated"
else
redirect_to :action => "show", :id => #user.id
end
end
private
def user_params
params.require(:user).permit(:email)
end
end
The validation code is okay according to rails guide. Please check whether the user name param is going to the controller. If the param goes empty, the presence true validation fails and may give that error.
Also see whether the sanitized attributes are correctly defined in application controller for username.(Devise)
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 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.