ruckus / quickbooks-ruby

Quickbooks Online REST API V3 - Ruby
MIT License
374 stars 302 forks source link

oauth2_client is undefined #506

Closed dustinschaerer closed 3 years ago

dustinschaerer commented 4 years ago

I'm trying to update our Quickbooks Integration to using Oauth2 and I keep getting an error in my quickbooks_controller that oauth2_client is undefined.

I'm also not sure what the grant_url instructions in README are referring to. Under this workflow, isn't the grant_url what's returned from the oauth2_client.auth_code.authorize_url call?

It seems like the Rails initializer is not setting that variable to be accessible in my controller.

ruckus commented 4 years ago

Post your code?

dustinschaerer commented 4 years ago

I've tried a few methods.

The first was to use a Rails initiailizer, "config/initiailizers/quickbooks_oauth.rb": oauth_params = { site: "https://appcenter.intuit.com/connect/oauth2", authorize_url: "https://appcenter.intuit.com/connect/oauth2", token_url: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer" } oauth2_client = OAuth2::Client.new(ENV['OAUTH_CLIENT_ID'], ENV['OAUTH_CLIENT_SECRET'], oauth_params)

quickbooks_controller.rb

def authenticate

    oauth_params = {
      site: "https://appcenter.intuit.com/connect/oauth2",
      authorize_url: "https://appcenter.intuit.com/connect/oauth2",
      token_url: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
    }
    oauth2_client = OAuth2::Client.new(ENV['OAUTH_CLIENT_ID'], ENV['OAUTH_CLIENT_SECRET'], oauth_params)

    redirect_uri = "https://methowreservations.com/quickbooks/oauth_callback"

     grant_url = oauth2_client.auth_code.authorize_url(redirect_uri: redirect_uri, response_type: "code", state: SecureRandom.hex(12), scope: "com.intuit.quickbooks.accounting")
     redirect_to grant_url
   end

   # https://methowreservations.com/quickbooks/oauth_callback?code=AB11581719107of59Ui0ykhqs5v2w9TCIMeUt7M3cK8TpZRlbr&state=81455b781ef7a2b20c6792a2&realmId=9130347936512676
   def oauth_callback
     if params[:state].present? 
       redirect_uri = "https://methowreservations.com/quickbooks/oauth_callback"
       if resp = oauth2_client.auth_code.get_token(params[:code], redirect_uri: redirect_uri)

         existing_service = ThirdPartyService.where( realm_id: params[:realmId] ).first        

         if existing_service.present?
           existing_service.access_token = resp.token
           existing_service.refresh_token = resp.refresh_token
           existing_service.save
         else
           ThirdPartyService.new(name: 'quickbooks', 
                                 realm_id: params[:realmId], 
                                 access_token: resp.token, 
                                 refresh_token: resp.refresh_token,
                                 access_token_expires_at: DateTime.now + 58.minutes, 
                                 refresh_token_expires_at: DateTime.now + 24.hours).save
         end

         flash.notice = "Your QuickBooks account has been successfully linked."
         @msg = 'Redirecting. Please wait.'
         @url = '/operator/process_tab'
         render 'close_and_redirect', layout: false  
       end
     end
   end

oauth2_client doesn't seem to stay set between the controller actions.

I was able to make to headway (hackety-hackety) if I reinitialized the oauth2_client object inside my controller's authenticate action, but the object was gone once it hits the oauth_callback method.

Thanks!

ruckus commented 4 years ago

Local variables created in a Rails initializer are not visible outside that initializer. You could make them Ruby Globals by using dollar sign variables,

$oauth2_client = ...

But using global variables is a bad practice.

If you are creating the client in a controller what about using a before_filter to create it as an instance variable thats visible to other actions?

dustinschaerer commented 4 years ago

Thank you! I'll give that a whirl. I was trying to follow the README but I obviously tripped up a bit.

pooriajr commented 4 years ago

I had the same confusion from the readme. Putting that code block in an initializer didn't make oauth2_client it accessible in my controllers, jobs, etc.

Is there a good way to initialize ouath2_client once and make it available across ALL of the app?

ruckus commented 4 years ago

@pjrashidi @dustinschaerer exact implementation details depend on your app, but what I have done is:

  1. In a Rails initializer load the configuration variables into environment variables. I have them in a YAML config file so I need to load them into ENV. but if you were using a platform like Heroku or AWS Beanstalk they might already be in env vars.

Something like:

oauth2_config = YAML.load_file(File.join(Rails.root, 'config/intuit_oauth2.yml'))[Rails.env]
ENV['INTUIT_OAUTH2_CLIENT_ID'] = oauth2_config['client_id']
ENV['INTUIT_OAUTH2_CLIENT_SECRET'] = oauth2_config['client_secret']

Then in a mixin which gets mixed-into models or controllers I have

    def construct_oauth2_client
      options = {
        site: "https://appcenter.intuit.com/connect/oauth2",
        authorize_url: "https://appcenter.intuit.com/connect/oauth2",
        token_url: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
      }
      if Rails.env.development?
        options[:connection_opts] = {
          proxy: "http://127.0.0.1:8888"
        }
      end
      OAuth2::Client.new(ENV['INTUIT_OAUTH2_CLIENT_ID'], ENV['INTUIT_OAUTH2_CLIENT_SECRET'], options)
    end

Then I can just call that method from anywhere and get hold of a valid client.

gregblass commented 4 years ago

Documentation needs updating here. I was like...how is this documentation acting like what is defined in an initializer would be available in a controller action?

gregblass commented 4 years ago

Thank you! I'll give that a whirl. I was trying to follow the README but I obviously tripped up a bit.

Nah...the docs should be updated. Maybe I'll give it a whirl. Because as they stand, what they are telling users to do won't work. And if I was 5+ years younger, I would be completely lost.

gregblass commented 4 years ago

@ruckus There we go. That's more like it. Something like what you posted should be added to the readme.

Since it is almost 4PM on a Friday, and my brain is almost completely dead, I don't have the mental capacity to do that right now. But maybe I'll give a PR a go when I get a chance.