Go back to the main page

Migrating a QuickBooks Online App from OAuth1 to OAuth2 using the qbo_api gem - Step 2

 

Show notes:

  • Add gem 'rack-oauth2' to Gemfile
  • Enable OAuth2 on your app at https://developer.intuit.com and set the redirect URI and grab the client_id and client_secret.
  • Initial OAuth2 Setup

config/routes.rb

  get '/oauth2-redirect', to: 'visitors#oauth2_redirect'

app/models/qbo/oauth2.rb


module Qbo
    class OAuth2

        def self.renew!(q)
            cl = client
            cl.refresh_token = q.refresh_token
            if resp = cl.access_token!
                attrs = { access_token: resp.access_token, refresh_token: resp.refresh_token }.merge(expires_in)
                q.update!(attrs)
                q.reload
            else
                msg = "FAILED_OAUTH2_RENEW_TOKEN: line: #{__LINE__} qbo_account: #{q.id} error_message: #{resp}"
                Rails.logger.warn msg
            end
        rescue => e
            msg = "FAILED_OAUTH2_RENEW_TOKEN: line: #{__LINE__} qbo_account: #{q.id} error_message: #{e.message}"
            Rails.logger.warn msg 
            raise e
        end

        def self.revoke!(refresh_token)
            revoke_endpoint = "https://developer.api.intuit.com/v2/oauth2/tokens/revoke"
            http_client = Rack::OAuth2.http_client
            http_client.post(revoke_endpoint, { "token" => refresh_token }, basic_auth_header)
        end

        def self.migrate!(qbo_account)
            QboApi.request_id = false
            api = Qbo.init(qbo_account)
            prefix = Rails.env.production? ? 'developer' : 'developer-sandbox'
            migration_uri = "https://#{prefix}.api.intuit.com/v2/oauth2/tokens/migrate"
            params = { scope: 'com.intuit.quickbooks.accounting', redirect_uri: redirect_url,
                                 client_id: client_id, client_secret: client_secret }
            if resp = api.request(:post, path: migration_uri, payload: params)
                attrs = { access_token: resp["access_token"], refresh_token: resp["refresh_token"] }.merge(expires_in)
                qbo_account.update!(attrs)
            end
        ensure
            QboApi.request_id = true
        end

        def self.client
            Rack::OAuth2::Client.new(
                identifier: client_id,
                secret: client_secret,
                redirect_uri: redirect_url,
                authorization_endpoint: "https://appcenter.intuit.com/connect/oauth2",
                token_endpoint: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
            )
        end

        def self.authorize_url(state:)
            client.authorization_uri(
                client_id: client_id,
                scope: 'com.intuit.quickbooks.accounting', 
                redirect_uri: redirect_url,
                response_type: 'code',
                state: state
            )
        end

        def self.redirect_url
            Rails.application.routes.url_helpers.oauth2_redirect_url(host: hostname)
        end

        def self.expires_in
            { 
                access_token_expires_in: 1.hour.from_now, 
                refresh_token_expires_in: 101.days.from_now 
            }
        end

        def self.client_id
            Rails.application.secrets.qbo_api_client_id
        end

        def self.client_secret
            Rails.application.secrets.qbo_api_client_secret
        end

        def self.basic_auth_header
            cred = ["#{client_id}:#{client_secret}"].pack('m').tr("\n", '')
            { 'Authorization' => "Basic #{cred}" }
        end

        def self.hostname
            if Rails.env.production?
                'example.com'
            else
                'example.test'
            end
        end
    end
end

app/controllers/visitors_controller.rb


class VisitorsController < ApplicationController  skip_before_action :authenticate_user!, :except => [:oauth2_redirect]

    def oauth2_redirect
        qbo_account = current_account.qbo_account
        if params[:state] == session[:oauth2_state] && qbo_account
            client = Qbo::OAuth2.client
            client.authorization_code = params[:code]
            if resp = client.access_token!
                attrs = {
                    access_token: resp.access_token,
                    refresh_token: resp.refresh_token,
                    companyid: params[:realmId]
                }.merge(Qbo::OAuth2.expires_in)
                qbo_account.update!(attrs)
                msg = "Your QuickBooks account has been successfully linked."
                flash[:inner_notice] = msg
                render 'shared/close_and_redirect', layout: 'basic'
            end
        end
    rescue => e
        @url = account_url(current_account)
        msg = "There was a problem linking Your QuickBooks account - given an error: #{e.message}"
        logger.warn msg
        flash[:alert] = msg
        render 'shared/close_and_redirect', layout: 'basic'
    end
end

app/controllers/application_controller.rb

  
def set_oauth2_state
    session[:oauth2_state] = Rails.env.test? ? '3242adf32423kjo' : SecureRandom.uuid
end
helper_method :set_oauth2_state

config/initializers/inflectors.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|  
  inflect.acronym 'OAuth2'
end

view code


<% content_for :script do %>
  intuit.ipp.anywhere.setup({ grantUrl: '<%= Qbo::OAuth2.authorize_url(state: set_oauth2_state).html_safe %>',
                              datasources: { quickbooks : true, payments : false }
                            });
<% end %>