Devise omniauthable breaks Omniauth authentication

2020-03-26 03:45发布

In my project, I have two type of users: job seekers and hiring managers. Job seekers don't have a model, they are just able to apply for jobs using the data received from from third-party providers while authenticating thru Omniauth. Hiring managers' info is stored in devise User model. Hiring managers also must be able to sign in with their company's Google email account. So, first I built job seekers' authentication using Omniauth 1.0.0, Rails 3.1.3:

omniauth.rb

require 'omniauth-openid'
require 'openid/store/filesystem'
Rails.application.config.middleware.use OmniAuth::Builder do
   provider :openid, :store => OpenID::Store::Filesystem.new('./tmp'), :name => 'google', :identifier => 'https://www.google.com/accounts/o8/id'
   provider :facebook, "xxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  {:scope => 'email, offline_access, publish_stream', :client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}}
   provider :twitter, "xxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
   provider :linkedin, "xxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxx"
 end

in routes.rb:

match '/auth/:provider/callback', :to => 'sessions#authenticate_jobseeker'
match '/auth/failure', :to => 'sessions#failure'

in sessions_controller.rb

def authenticate_jobseeker
  session[:jobseeker] = request.env['omniauth.auth']

  if valid_job_seeker?
    redirect_to new_job_application_path(...)
  else
    redirect_to request.env['omniauth.origin'] || root_path, alert: "Authentication failure"
  end
end

Up to this point everything worked fine. However, when I started implementing Google sign on for User model, and added :omniauthable to it, my job seeker authentication broke. I am using Devise 1.5.2:

user.rb

class User < ActiveRecord::Base
  #...
  devise :database_authenticatable, :registerable,
         ... :lockable, :omniauthable
  #...
end

in devise.rb:

config.omniauth :open_id, :store => OpenID::Store::Filesystem.new('./tmp'), :name => 'google', :identifier => 'https://www.google.com/accounts/o8/id', :require => 'omniauth-openid'

in routes.rb:

devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" } do
  get '/users/auth/:provider' => 'users/omniauth_callbacks#passthru'
end

At this point, Users' authentication worked, but job seekers' did not. After searching for a while, the issue was fixed by adding :path_prefix => "/auth" to every provider in omniauth.rb. The only problem now, is when job seeker does not allow access to its data (i.e. presses "Don't Allow" and comes back to the application), I get following RuntimeError for every provider:

Could not find a valid mapping for path "/auth/twitter/callback" 
Parameters:
{"denied"=>"mKjVfMRwRAN12ZxQ9cxCoD4rYSLJIRLnEqgiI"}

top of the trace:

devise (1.5.2) lib/devise/mapping.rb:48:in `find_by_path!'
devise (1.5.2) lib/devise/omniauth.rb:17:in `block in <top (required)>'
omniauth (1.0.0) lib/omniauth/strategy.rb:418:in `call'
omniauth (1.0.0) lib/omniauth/strategy.rb:418:in `fail!'
omniauth-oauth (1.0.0) lib/omniauth/strategies/oauth.rb:63:in `rescue in callback_phase'
omniauth-oauth (1.0.0) lib/omniauth/strategies/oauth.rb:45:in `callback_phase'
omniauth (1.0.0) lib/omniauth/strategy.rb:200:in `callback_call'
omniauth (1.0.0) lib/omniauth/strategy.rb:166:in `call!'
omniauth (1.0.0) lib/omniauth/strategy.rb:148:in `call'
omniauth (1.0.0) lib/omniauth/strategy.rb:168:in `call!'
omniauth (1.0.0) lib/omniauth/strategy.rb:148:in `call'
omniauth (1.0.0) lib/omniauth/strategy.rb:168:in `call!'
omniauth (1.0.0) lib/omniauth/strategy.rb:148:in `call'
omniauth (1.0.0) lib/omniauth/builder.rb:30:in `call'

I've been trying to solve it for a while now. Any help is greatly appreciated. Let me know, if I can provide additional info.

2条回答
聊天终结者
2楼-- · 2020-03-26 04:20

Are you using omniauth["user_info"] in your models somewhere? In my case, I was accessing

omniauth["user_info"]["email"] 

and that would crash and I would get the same error, being caught by devise.

In my app as well, we use omniauth directly (for businesses) as well as use device+facebook for user logins.

Havent yet figured out to not get failure caught by devise though. Devise registers it's own failure app. Will update when i figure it out.

Update: I'm sorry it seems I misread part of your question. You can see a clear failure to authorize from the remote webapp which seems to stuff up and not a masked exception from the code (as was in my case).

查看更多
来,给爷笑一个
3楼-- · 2020-03-26 04:37

Answering my own question. So, final decision was to go with pure Omniauth implementation. I removed :omniauthable from User model, removed config.omniauth... from devise.rb, removed :omniauth_callbacks devise routes from routes.rb. So, all users (no matter what role) would use ame callback routes and hit sessions_controller#authenticate_jobseeker action (should consider renaming the action?):

def authenticate_jobseeker
  auth_hash = request.env['omniauth.auth']

  unless auth_hash.present?
    redirect_to request.env['omniauth.origin'] || root_path, alert: "Sorry, we were not able to authenticate you" and return
  end

  @user = User.find_from_oauth(auth_hash)
  if @user.present?
    flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
    sign_in_and_redirect @user, :event => :authentication and return
  else
    session[:jobseeker] = auth_hash["info"]
    if valid_job_seeker?
      redirect_to new_job_application_path(...)
    end
  end
end

and User.find_from_oauth:

def self.find_from_oauth(auth_hash)
  if auth_hash
    user = User.where(:email => auth_hash["info"]["email"]).first
  end
  user
end

This implementation satisfied all of the requirements.

查看更多
登录 后发表回答