Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails, Devise & Omniauth - problems with setup

I am trying (again) to set up authentications with Rails 4, devise and omniauth.

I tried to follow the example in this post: Rails 4, Devise, Omniauth (with multiple providers)

I have these gems installed:

gem 'devise'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin-oauth2'
gem 'oauth2'

I have a user model, and an authentications model.

I have:

User.rb:

  has_many :authentications

def disapprove
    self.approved = false
  end

  def approve
    self.approved = true
  end

SOCIALS = { facebook: 'Facebook', google_oauth2: 'Google', linkedin: 'Linkedin' }

def self.from_omniauth(auth, current_user)
  authentication = Authentication.where(:provider => auth.provider,
                                  :uid => auth.uid.to_s, 
                                  :token => auth.credentials.token, 
                                  :secret => auth.credentials.secret).first_or_initialize
  authentication.profile_page = auth.info.urls.first.last unless authentication.persisted?
  if authentication.user.blank?
    user = current_user.nil? ? User.where('email = ?', auth['info']['email']).first : current_user
    if user.blank?
      user = User.new
      user.skip_confirmation!
      user.password = Devise.friendly_token[0, 20]
      user.fetch_details(auth)
      user.save
    end
    authentication.user = user
    authentication.save
  end
  authentication.user 
end  

def fetch_details(auth)
  self.first_name = auth.info.first_name
  self.last_name = auth.info.last_name
  self.email = auth.info.email
  self.image = URI.parse(auth.info.image)
end

Authentication.rb

 belongs_to :user

Routes.rb

devise_for :users,
             :controllers => {
:registrations => "users/registrations",


             :omniauth_callbacks => 'users/omniauth_callbacks',
           }

User/registrations_controller

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

  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
        @user = User.new(user_params) #(params[:user])

        respond_to do |format|
          if resource.save
            # Tell the UserMailer to send a welcome email after save
            # {@user.send_admin_mail
            # @user.send_user_welcome_mail}

            format.html { redirect_to(profile_path(@user.profile))}
            #, notice: 'We have received your registration. We will be in touch shortly.') }
            #format.json { render json: root_path, status: :created, location: @user }
          else
            #format.html { redirect_to(root_path, alert: 'Sorry! There was a problem with your registration. Please contact us to sort it out.') }
            format.html { render action: 'new' }
            format.json { render json: @user.errors, status: :unprocessable_entity }
          end
        end
      end

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



      # protected
      #   def after_sign_up_path_for(resource)
      #     'subscribers/new'
      #   end
end

User/Omniauth_callbacks controller

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

    # try again following https://stackoverflow.com/questions/21249749/rails-4-devise-omniauth-with-multiple-providers

  def all
    user = User.from_omniauth(env['omniauth.auth'], current_user)
    if user.persisted?
      sign_in user
      flash[:notice] = t('devise.omniauth_callbacks.success', :kind => User::SOCIALS[params[:action].to_sym])
      if user.sign_in_count == 1
        redirect_to profile_path(@user.profile)
      else
        redirect_to root_path
      end
    else
      session['devise.user_attributes'] = user.attributes
      redirect_to root_path
    end
  end

  User::SOCIALS.each do |k, _|
    alias_method k, :all
  end
end

The devise/new registrations view says:

<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

    <div class="row">
        <div class="col-md-3 col-md-offset-3">
            <div class="row">
                <div class="col-md-12">

                  <%- if devise_mapping.omniauthable? %>
                          <div class="facebookauth">
                            <%= link_to "Join with Facebook", user_omniauth_authorize_path(:facebook) %>
                          </div>
                  <% end -%>

                </div>
              </div>


          <div class="row">
            <div class="col-md-12">

              <%- if devise_mapping.omniauthable? %>
                  <div class="googleauth">
                    <%= link_to "Join with Google", user_omniauth_authorize_path(:google_oauth2) %>
                  </div>
              <% end -%>
            </div>
          </div>



      <div class="row">
              <div class="col-md-12">

                  <%- if devise_mapping.omniauthable? %>
                      <div class="linkedinauth">
                        <%= link_to "Join with LinkedIn", user_omniauth_authorize_path(:linkedin) %>
                      </div>
                  <% end -%>
              </div>
      </div>

          <div class="row">
            <div class="col-md-12">

              <%- if devise_mapping.omniauthable? %>
                  <div class="twitterauth">
                    <%= link_to "Join with Twitter", user_omniauth_authorize_path(:twitter) %>
                  </div>
              <% end -%>

            </div>
          </div>

        </div>

        <div class="col-md-5">
          <div class="emailform">
            <div class="form-inputs", style="margin-left: 7%">


                  <%= devise_error_messages! %>
                  <%= f.input :first_name,  :label_html => {:class => 'deviselabels'}, autofocus: true, required: false, :input_html => {:maxlength => 15, :size => 40, class: 'lineitemdevise'} %>
                  <%= f.input :last_name, :label_html => {:class => 'deviselabels'}, required: false, :input_html => {:maxlength => 15, :size => 40, class: 'lineitemdevise'} %>
                  <%= f.input :email, :label_html => {:class => 'deviselabels'}, required: false, autofocus: false, placeholder: "Please use your work or university address", :input_html => {:maxlength => 55, :size => 40, class: 'lineitemdevise'} %>
                  <%= f.input :password, :label_html => {:class => 'deviselabels'}, required: false, placeholder: "Minimum 8 characters", :input_html => {:maxlength => 15, :size => 40, class: 'lineitemdevise'} %>
                </div>

            <div class="form-actions">
          <%= f.button :submit, "Join by email", :class => "dcpb" %>
         </div>
          <% end %>
          </div>
      </div>

I have another model called profile.rb.

profile belongs_to user

Problems:

  1. None of this works. When I click on each of the social media login links, the page just jumps to the sign up by email form.

The heroku logs error message says:

(facebook) Authentication failure! invalid_credentials: OAuth2::Error, : 
2015-11-03T07:05:48.237549+00:00 app[web.1]: {"error":{"message":"Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100,"fbtrace_id":"HD3mnzmSTEw"}}
  1. When I complete the sign up by email form with an email and password, the user name is recognised (in that the navbar says Hello , but when I go into the rails console, the user is not listed.

Also, when I click on the user name, I get an error which says that profile does not exist. The heroku logs say:

(Couldn't find Profile with 'id'=3)
  1. Is there another step required to make the social media registrations work to create a new user?

MY NEXT ATTEMPT:

I've changed all of the above and tried again, following the approach in the Railscasts videos.

I now use a user model and an authentications model.

In the user.rb, I have:

  has_many :authentications, :dependent => :delete_all

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

end

authentication.rb

belongs_to :user

def self.from_omniauth(auth)
    where(auth.slice(:provider, :uid)).first_or_create do | user |
        authentication.provider = auth.provider
        authentication.uid = auth.uid
        authentication.user.first_name = auth.first_name
        authentication.user.last_name = auth.last_name
        authentication.user.image = auth.info.image

    end
  end

Authentications_controller:

class AuthenticationsController < ApplicationController
  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 authentications_url }
      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

In the routes.rb, I have:

 devise_for :users,
             :controllers => {
                :registrations => "users/registrations",
           }
 patch '/auth/:provider/callback' => 'authentications#create'

Omniauth.rb

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

OmniAuth.config.logger = Rails.logger

 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'}}

Then when I try this, I get this error:

(facebook) Authentication failure! invalid_credentials: OAuth2::Error, : 
2015-11-05T06:4839+00:00 app[web.1]: {"error":{"message":"Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100,"fbtrace_id":"CrvXN22Z"}}

I find the next part of the error message odd because it refers to the callbacks controller which I no longer use (the whole thing is commented out and there is no route for it).

Authentication failure! invalid_credentials: OAuth2::Error, : 
2015-11-05T08:24:16.010951+00:00 app[web.1]: Processing by Devise::OmniauthCallbacksController#failure as HTML
2015-11-05T08:24:16.012648+00:00 app[web.1]: Redirected to http://www.dder.com/users/sign_in

A FURTHER ATTEMPT

I have been trying to set up devise with omniauth for more than 1.5 years now. This is my latest attempt (following the Sitepoint tutorial at sitepoint.com/rails-authentication-oauth-2-0-omniauth). I've tried to use this tutorial before and not had any success, so I've made some tweaks to try and adapt it to some aspects of other tutorials on this topic.

I now have:

user.rb

  has_many :authentications, :dependent => :delete_all

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

  end

authentication.rb

belongs_to :user
  def self.from_omniauth(auth)
    where(auth.slice(:provider, :uid)).first_or_create do | user |
        authentication.provider = auth.provider
        authentication.uid = auth.uid
        authentication.user.first_name = auth.first_name
        authentication.user.last_name = auth.last_name
        authentication.user.image = auth.info.image

    end
  end

authentications controller

    class 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 authentications_url }
          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? # Suggestion from Sitepoint tutorial - not currently implemented because not sure about the difference between this and set params.

  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

routes

devise_for :users,
             :controllers => {
                :registrations => "users/registrations",
                :omniauth_callbacks => "authentications"
                # :omniauth_callbacks => 'users/omniauth_callbacks',
           }

get '/auth/:provider/callback' => 'authentications#create'

I can check these routes with:

rake routes | grep auth
                      user_omniauth_authorize GET|POST /users/auth/:provider(.:format)                                                                 authentications#passthru {:provider=>/facebook|linkedin|twitter|google_oauth2/}
                       user_omniauth_callback GET|POST /users/auth/:action/callback(.:format)                                                          authentications#:action
                                              GET      /auth/:provider/callback(.:format)                                                              authentications#create

new registration partial in the view

<%- if devise_mapping.omniauthable? %>
                  <div class="twitterauth">
                    <%= link_to "Join with Twitter", user_omniauth_authorize_path(:twitter) %>
                  </div>
              <% end -%>

I'm really not sure where this path is coming from. Not sure why it's named as it is.

new session partial in the view

 <%- if devise_mapping.omniauthable? %>
                  <div class="twitterauth">
                    <%= link_to "Login with Twitter", user_omniauth_authorize_path(:twitter) %>
                  </div>
              <% end -%>

Current error:

AbstractController::ActionNotFound at /users/auth/twitter/callback

The action 'twitter' could not be found for AuthenticationsController
like image 970
Mel Avatar asked Nov 03 '15 07:11

Mel


People also ask

What does devise do in Rails?

Devise is the cornerstone gem for Ruby on Rails authentication. With Devise, creating a User that can log in and out of your application is so simple because Devise takes care of all the controllers necessary for user creation ( users_controller ) and for user sessions ( users_sessions_controller ).

Does devise work with Rails 7?

Our out-of-the box Devise setup is now working with Rails 7. Once again, if you'd like to refer to any of the code for this setup, or use the template wholesale for a new app, the code is available on GitHub, and you may also use it as a template repo to kick off your own Rails 7 devise projects.

What does devise Authenticate_user do?

Devise offers authentication helper functions for Rails controllers and views. They include: authenticate_user! : This helper function denies controller access to unauthenticated users. user_signed_in? : This returns a boolean that indicates whether or not a user is signed in.


1 Answers

Devise comes with an out of the box solution for integration with omniuth. You can checkout these urls: 1. https://www.digitalocean.com/community/tutorials/how-to-configure-devise-and-omniauth-for-your-rails-application This one shows integration with DigitalOcean but can be extended to others. 2. https://github.com/plataformatec/devise/wiki/OmniAuth%3A-Overview This one is from devise wiki

Hope it helps

like image 148
udit mittal Avatar answered Oct 30 '22 05:10

udit mittal