Devise Omniauth - setup & defining strategies

2019-06-22 05:35发布

问题:

I tried asking this question - and didn't find any help.

http://stackoverflow.com/questions/33493369/rails-devise-omniauth-problems-with-setup

I gave up trying to solve the problem and made an entirely new app with nothing else in it so that I could try to reduce the scope for other errors interfering.

In my new app, I now get an error when I click on the new registration/new user link.

I have followed the rails casts tutorials (having tried about 20 others) to get this setup. 1.5 years in and I am still struggling.

The problem is with the way the strategies are defined in the controller. I have 4 strategies (twitter, Facebook, google and linkedin) and am I am currently getting a different error for each of them when I try clicking on the links to create a new registration with those accounts:

For Twitter: Unknown action The action 'twitter' could not be found for Users::AuthenticationsController

For Facebook:

Given URL is not permitted by the Application configuration: One or more of the given URLs is not permitted by the App's settings. It must match the Website URL or Canvas URL, or the domain must be a subdomain of one of the App's domains.

This error is because I can't figure out Facebook's documentation. It shows how to use js to login with Facebook where I am trying to use gems in Rails. I'm not worried about this error. I think it has something to do with my website not being defined as local host, although I can't find anywhere to but a callback url in Facebook's developer console. My question doesn't relate to this problem, I'll find help to sort this out another time.

For LinkedIn:

Safari can’t open the page “https://www.linkedin.com/uas/oauth2/authorization?client_id=LINKEDIN_DEV_ID&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fusers%2Fauth%2Flinkedin%2Fcallback&response_type=code&scope=r_basicprofile+r_emailaddress&state=3fef961c10410464cd5b0ca49b25112ce53fb65f1a3c794f”.

The error is: “cannot decode raw data” (NSURLErrorDomain:-1015) I have an oauth2 callback defined as:

http://localhost:3000/users/auth/linkedin/callback

I don't know what's broken here.

For Google:

translation missing: en.devise.authentications.user.failure

The new registrations view just refreshes when i click this link and an error message says the above. I don't know what's causing this error either.

Each of the errors is different.

I have a folder in my controllers folder called users. Inside that I have two controllers as follows:

Authentications controller:

class Users::AuthenticationsController < Devise::OmniauthCallbacksController
  before_action :set_authentication, only: [:destroy]

  def index
    @authentications = current_user.authentications if current_user
  end


  def create
    omniauth = request.env["omniauth.auth"]
    authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
    if authentication
      sign_in_and_redirect_user(:user, authentication.user.profile)

    elsif current_user
      current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
      redirect_to user.profile_url
    else
      user = User.new
      user.omniauth(omniauth)
      if user.save!
        sign_in_and_redirect_user(:user, user.profile)
      else
        session[:omniauth] = omniauth.except('extra')
        redirect_to new_user_registration_url
      end
    end  
  end

  def destroy
    @authentication.destroy
    respond_to do |format|
      format.html { redirect_to root_path }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_authentication
      @authentication = current_user.authentications.find(params[:id])
    end

end

Registrations controller:

class Users::RegistrationsController < Devise::RegistrationsController 
  #before_filter :check_permissions , :only => [ :new, :create, :cancel ] 
  #skip_before_filter :require_no_authentication 
  # before_action :configure_permitted_parameters, if: :devise_controller?

  def check_permissions
    authorize! :create, resource
  end

  def index
    if params[:approved] == "false"
      @users = User.find_all_by_approved(false)
    else
      @users = User.all
    end
  end

  def create
    super
    session[:omniauth] = nil unless @user.new_record?
  end

  # THIS IS A SUGGESTION FROM SITEPOINT TUTORIAL 
  # protected

  #   def configure_permitted_parameters
  #     devise_parameter_sanitizer.for(:sign_up) << [:first_name, :last_name]
  #   end


  private
    def user_params
          params.require(:user).permit(:first_name, :last_name, :email, :password )
    end

    def build_resource(*args)
        super
        if session[:omniauth]
        @user.apply_omniauth(session[:omniauth])
        @user.valid?
        end
    end  

end

User.rb has

devise
:omniauthable, :omniauth_providers => [:facebook, :linkedin, :twitter, :google_oauth2 ]


 has_many :authentications, :dependent => :delete_all


def apply_omniauth(omniauth)
        self.email = auth['extra']['raw_info']['email']
        authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'], :token => auth['credentials']['token'])
    end

def password_required?
  (authentications.empty? || !password.blank?) && super
end

In my routes.rb I have:

  devise_for :users,
             :controllers => {
                :registrations => "users/registrations",
                :omniauth_callbacks => "users/authentications"
                # :omniauth_callbacks => 'users/omniauth_callbacks',
           }
  get '/auth/:provider/callback' => 'users/authentications#create'
  get '/sign_out', :to => 'users/authentications#destroy'         

In my omniauth.rb I have:

require 'omniauth-facebook'
require 'omniauth-google-oauth2'
require 'omniauth-twitter'



OmniAuth.config.logger = Rails.logger

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
end

Rails.application.config.middleware.use OmniAuth::Builder do
   provider :facebook, ENV['FACEBOOK_ID'], ENV['FACEBOOK_KEY'],
     :scope => 'public_profile', info_fields: 'id,first_name,last_name,link,email',
     :display => 'popup',
     :client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}

end

Rails.application.config.middleware.use OmniAuth::Builder do
   provider :google_oauth2, ENV['YT_CLIENT_ID'], ENV['YT_CLIENT_SECRET'],
            scope: 'profile', image_aspect_ratio: 'square', image_size: 48,
            # {name: "google_login", approval_prompt: ''},
            access_type: 'online'
            #ENV["GOOGLE_APP_ID"], ENV["GOOGLE_APP_SECRET"]
#   {
#         :name => "google",
#         :scope => "userinfo.email, userinfo.profile, plus.me, http://gdata.youtube.com",
#         :prompt => "select_account",
#         :image_aspect_ratio => "square",
#         :image_size => 50
#       }

end

Rails.application.config.middleware.use OmniAuth::Builder do
   if Rails.env == 'production'
     key = ENV["LINKEDIN_PRODUCTION_KEY"]
     secret = ENV["LINKEDIN_PRODUCTION_SECRET"]
   else
     key = "LINKEDIN_DEV_ID"
     secret = "LINKEDIN_DEV_KEY"
   end

   provider :linkedin, key, secret,
     :scope => "r_basicprofile r_emailaddress", 
     :field => ["id", "email-address", "first-name", "last-name" ],
     :client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}} 
end

In my new registration/session views, I have:

<% if devise_mapping.omniauthable? %>
                            <%= link_to icon('facebook', id: 'facebookauth'), user_omniauth_authorize_path(:facebook) %>

                            <%= link_to  icon('google', id: 'googleauth'), user_omniauth_authorize_path(:google_oauth2) %>

                            <%= link_to icon('linkedin', id: 'linkedinauth'), user_omniauth_authorize_path(:linkedin) %>

                            <%= link_to icon('twitter', id: 'twitterauth'), user_omniauth_authorize_path(:twitter) %>
                        <% end %>   

I think it has something to do with not having named strategies the authentications controller. In other tutorials, I have set up the following (now commented out of the controller because there is a method called apply_omniauth used now).

# def facebook
#   #   @user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
#   #   if @user.persisted?
#   #     # @user.send_admin_mail
#   #     # @user.send_user_welcome_mail

#   #     sign_in @user, :event => :authentication #this will throw if @user is not activated

#   #     if @user.profile
#   #       redirect_to profile_path(@user.profile)
#   #     else
#   #       redirect_to new_profile_path
#   #     end

#   #     # 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 root_path
#   #   end
#   # end


#   # def linkedin
#   #   @user = User.find_for_linkedin_oauth(request.env["omniauth.auth"])

#   #   if @user.persisted?
#   #     # @user.send_admin_mail
#   #     # @user.send_user_welcome_mail

#   #     sign_in @user, :event => :authentication

#   #     if @user.profile
#   #       redirect_to profile_path(@user.profile)
#   #     else
#   #       redirect_to new_profile_path
#   #     end
#   #     #  set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
#   #   else
#   #     session["devise.linkedin_data"] = request.env["omniauth.auth"]
#   #     redirect_to root_path
#   #   end
#   # end


#   # def twitter
#   #   begin
#   #     @user = User.from_omniauth(request.env['omniauth.auth'])
#   #     session[:user_id] = @user.id
#   #     flash[:success] = "Welcome, #{@user.name}!"
#   #   rescue
#   #     flash[:warning] = "There was an error while trying to authenticate you..."
#   #   end
#   #   redirect_to new_profile_path #root_path
#   # end

#   # #
#   def google_oauth2
#      # You need to implement the method below in your model (e.g. app/models/user.rb)
#      @user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)

#      if @user.persisted?
#         sign_in @user, :event => :authentication #this will throw if @user is not activated
#         if @user.profile
#           redirect_to profile_path(@user.profile)
#           flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
#         else
#           redirect_to new_profile_path
#         end

#      else
#        session["devise.google_data"] = request.env["omniauth.auth"]
#        redirect_to new_user_registration_url
#      end
#   end

Before I completely ditch the rails cast setup, is there a way I can point the current structure towards the named providers using the code I already have?

ANOTHER ATTEMPT:

So I ditched the RailsCast. I tried to follow this tutorial:

http://willschenk.com/setting-up-devise-with-twitter-and-facebook-and-other-omniauth-schemes-without-email-addresses/

I'm confused by the reference to adding a new 'class' called FormUser. I made a new model called form_user.rb and added the content as described. I'm using Rails 4. I don't know what atto-accessor means, but I have it in my file as demonstrated. I don't have a corresponding controller. I also don't have associations defined in my user model (which I think this belongs to).

Anyway, I have followed the setup and now get this error:

    TypeError
superclass mismatch for class OmniauthCallbacksController

It's the same for each of the strategies defined in my app.

Has anyone seen this particular error & have any tips for how to resolve it?

The only difference between my setup and the tutorial is that in my controllers, I have a folder called users, in that, I have a registrations controller and an omniauth_callbacks controller. My routes have been adjusted to reflect this layer.

devise_for :users, class_name: 'FormUser',
             :controllers => {
                :registrations => "users/registrations",
                # :omniauth_callbacks => "users/authentications"
                :omniauth_callbacks => 'users/omniauth_callbacks',
           }
  get '/auth/:provider/callback' => 'users/authentications#create'
  get '/authentications/sign_out', :to => 'users/authentications#destroy' 


  devise_scope :user do
    get '/users/auth/:provider/upgrade' => 'users/omniauth_callbacks#upgrade', as: :user_omniauth_upgrade
    get '/users/auth/:provider/setup', :to => 'users/omniauth_callbacks#setup'
  end

回答1:

If anyone else is tearing their hair out trying to figure this out - I don't have the answers for how to do it, but I do know that the Railscasts are so old they are not useful. There are several parts of the code above that no longer match the gem config. Following those tutorials will not help you. Over the past 1.5 years I've tried to follow at least 20 different tutorials and am yet to find one that I can get to work. Errors in the rails cast include the redirect (doesnt require reference to 'user' any more amongst many others. Give up if you're relying on the Rails Cast. I'd really appreciate if anyone has any insights into where to find a CURRENT tutorial.