1dolinski
1dolinski

Reputation: 549

Upgraded to Rails 6 - Devise OmniAuth Twitter NoMethodError

Upgraded Rails to 6 along with devise.

After updating for this CSRF error https://github.com/heartcombo/devise/issues/5236 arrived at:

Started POST "/users/auth/twitter" for ::1 at 2022-09-22 21:24:44 -0400
(twitter) Request phase initiated.
(twitter) Authentication failure! undefined method `downcase' for nil:NilClass

        "#{u.scheme.downcase}://#{u.host.downcase}#{(u.scheme.casecmp("http").zero? && u.port != 80) || (u.scheme.casecmp("https").zero? && u.port != 443) ? ":#{u.port}" : ""}#{u.path && u.path != "" ? u.path : "/"}"
                                        ^^^^^^^^^: NoMethodError, undefined method `downcase' for nil:NilClass

Processing by Users::OmniauthCallbacksController#failure as HTML

Gemfile:

ruby "3.1.0"
gem 'rails', '6.0.3.1'

gem 'devise'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem "omniauth-rails_csrf_protection", "~> 1.0"

link_to for CSRF with POST, previously GET

<%= link_to "Log In" user_twitter_omniauth_authorize_path  method: :post, "data-turbo": false %>

In omniauth.rb

include Devise::OmniAuth::UrlHelpers

OmniAuth.config.allowed_request_methods = [:post]
OmniAuth.config.logger = Rails.logger if Rails.env.development?

OmniAuth.config.full_host = Rails.env.production? ? 'https://www.website.com' : 'http://localhost:3000'


Rails.application.config.middleware.use OmniAuth::Builder do
    provider :facebook, ENV["FACEBOOK_KEY"], ENV["FACEBOOK_SECRET"], callback_url: ENV["FACEBOOK_CALLBACK"]

    provider :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET'], {
        secure_image_url: true,
        image_size: 'original'
      }
  
    provider :apple, ENV['APPLE_CLIENT_ID'], '', {
      scope: 'email name',
      team_id: ENV['APPLE_TEAM_ID'],
      key_id: ENV['APPLE_KEY_ID'],
      pem: Base64.strict_decode64(ENV['APPLE_P8_BASE64'])
    }
end
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def self.provides_callback_for(provider)
      class_eval %Q{
        def #{provider}
          
          
          @user = User.find_for_oauth(request.env["omniauth.auth"], current_user)
          skip_authorization
          authorize @user, policy_class: OmniauthCallbackPolicy
          if @user.persisted?
            
            @user.remember_me
            sign_in_and_redirect @user, event: :authentication
            set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
          else
            
            session["devise.#{provider}_data"] = request.env["omniauth.auth"]
            redirect_to new_user_registration_url
          end
        end
      }
    end
    
    [:twitter, :facebook, :apple].each do |provider|
      provides_callback_for provider
    end
    
    def failure
      redirect_to root_path
    end
  end

routes.rb

  devise_for :users, defaults: { format: :html }, controllers: { omniauth_callbacks: 'users/omniauth_callbacks', passwords: 'users/passwords' }

The line of code that errors our seems to be defined here https://rubydoc.info/gems/oauth/0.5.5/OAuth%2FRequestProxy%2FBase:normalized_uri

Upvotes: 6

Views: 540

Answers (2)

juanca
juanca

Reputation: 246

TLDR;

To solve this, just upgrade the oauth gem to the latest version.

bundle update oauth

Long version XD

I was with the same problem after a recent upgrade but in my case my model was not managed with devise, so I decided to look further; after a long debugging session with the involved gems following the traceback, I found a bug in the initialize method of the Consumer class on the OAuth module in oauth-1.0.0/lib/oauth/consumer.rb

# ensure that keys are symbols

@options = @@default_options.merge(options.transform_keys(&:to_sym))

The problem was that after the merge the keys site and authorize_path of the @@default_options were still defined as strings

{"authorize_path"=>"/oauth/authenticate", "site"=>"https://api.twitter.com", "proxy"=>nil}

So the rest of the code and libs receiving these options argument were not able to correctly find the options[:site] used to get the endpoint, thus returning something similar to:

http://:80/request_token

and as expected, parsing that string with URI.parse as is done in the oauth_full_request_uri method in oauth-1.0.0/lib/oauth/client/net_http.rb returns no host at all and raising the mentioned error in the question.

So I decided to make this minor change, and everything worked again.

@options = @@default_options.merge(options).transform_keys(&:to_sym)

I was preparing to make a pull request to help to solve this, and I found that the author of the lib moved the repo to GitLab and also found he already solved this issue XD in the latest version using the snaky_hash lib, take a look here:

https://gitlab.com/oauth-xx/oauth/-/commit/7110a74690729b9abf8005cf0f58eedfc64f6ca3

So just updating the gem should be enough. I hope this helps you guys!

Upvotes: 3

Tibic4
Tibic4

Reputation: 3767

The error could be caused because the Twitter API is returning a nil value for the user’s profile image. This is causing the error because the code is trying to call the downcase method on a nil value. The solution is to add the secure_image_url and image_size options to the Twitter provider in the omniauth.rb file. The secure_image_url option will ensure that the image URL is always returned as an HTTPS URL. The image_size option will ensure that the image URL is always returned as the original size.

Add the following to your omniauth.rb file:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET'], {
    secure_image_url: true,
    image_size: 'original'
  }
end

Upvotes: 0

Related Questions