Search code examples
ruby-on-railsgoogle-apigoogle-oauthgoogle-analytics-apigoogle-api-ruby-client

Using Google OAuth2 API with Ruby on Rails


I have a system with multiple customers accounts, each account has at least one website that has a Google Analytics account. I need my clients authorize my system to access Google Analytics accounts so I can generate an automatic monthly report page views and present them this information with other information of my system. I would like to do this without the need for the customers to authorize my application to their Google Analytics account again.

When the client authorizes access to Google Analytics, it runs for about 1 hour (which I believe to be the session time token expires_in: 3600). After this time I cannot access Google Analytics client. As you can see in the code I already have tried to use the access_type:: offline and update the client token with client.update_token! but I do not know how to do this. Could anyone tell me if what I'm trying to do is possible and show me how to do this so I can generate monthly reports without the client authorize my application again?

I created some methods in my controller (I know the code isn't good, it's just a test and maybe I should encrypt the access token before writing it to my database).

class ContractedProductsController < ApplicationController

    def analytics_connection
        client_info = {
                client_id: Rails.configuration.x.google_api['client_id'],
                client_secret: Rails.configuration.x.google_api['client_secret'],
                authorization_uri: Rails.configuration.x.google_api['authorization_uri'],
                scope: Google::Apis::AnalyticsV3::AUTH_ANALYTICS_READONLY,
                redirect_uri: url_for(action: :analytics_callback),
                state: Base64.encode64('{ "account": ' + params[:account_id] + ', "contracted_product": ' + params[:id] + ' }'),
                additional_parameters: { access_type: :offline, approval_prompt: :force }
        }
        client = Signet::OAuth2::Client.new(client_info)

        redirect_to client.authorization_uri.to_s
    end

    def analytics_callback
        client_info = {
                client_id: Rails.configuration.x.google_api['client_id'],
                client_secret: Rails.configuration.x.google_api['client_secret'],
                token_credential_uri: Rails.configuration.x.google_api['token_credential_uri'],
                redirect_uri: url_for(action: :analytics_callback),
                code: params[:code]
        }
        client = Signet::OAuth2::Client.new(client_info)
        response = client.fetch_access_token!

        session[:google_api] = response
        state = JSON.parse(Base64.decode64(params[:state]), object_class: OpenStruct)

        redirect_to account_contracted_product_analytics_list_path(state.account, state.contracted_product)
    end

    def analytics_list
        client = Signet::OAuth2::Client.new(access_token: session[:google_api]['access_token'])
        service = Google::Apis::AnalyticsV3::AnalyticsService.new
        service.authorization = client

        @account_summaries = service.list_account_summaries
    end

    def analytics_save
        api_integrations = [
                {
                        entity_type: ContractedProduct.name,
                        entity_id: @contracted_product.id,
                        key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:access_token],
                        value: session[:google_api]['access_token']
                },
                {
                        entity_type: ContractedProduct.name,
                        entity_id: @contracted_product.id,
                        key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:refresh_token],
                        value: session[:google_api]['refresh_token']
                },
                {
                        entity_type: ContractedProduct.name,
                        entity_id: @contracted_product.id,
                        key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:profile_id],
                        value: params[:profile_id]
                }
        ]
        @api_integration = ApiIntegration.create(api_integrations)

        respond_to do |format|
            if @api_integration
                format.html { redirect_to [@account, @contracted_product], notice: I18n.t('controllers.contracted_products.analytics_data_successfully_saved', default: 'Analytics data was successfully saved.') }
                format.json { render :show, status: :ok, location: [@account, @contracted_product] }
            else
                format.html { render :analytics_save, status: :ok, location: [@account, @contracted_product] }
                format.json { render json: @contracted_products_service.errors, status: :unprocessable_entity }
            end
        end
    end

    def analytics_report
        entity_conditions = { entity_type: ContractedProduct.name, entity_id: @contracted_product.id }
        api_integration_access_token = ApiIntegration.find_by entity_conditions.merge(key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:access_token])
        # api_integration_refresh_token = ApiIntegration.find_by entity_conditions.merge(key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:refresh_token])
        api_integration_profile_id = ApiIntegration.find_by entity_conditions.merge(key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:profile_id])

        client = Signet::OAuth2::Client.new(access_token: api_integration_access_token.value)
        service = Google::Apis::AnalyticsV3::AnalyticsService.new
        service.authorization = client
        profile_id = 'ga:' + api_integration_profile_id.value
        start_date = Date.today.at_beginning_of_month.last_month.to_s
        end_date = Date.today.at_beginning_of_month.last_month.to_s
        metrics = 'ga:pageviews'
        dimensions = {
                dimensions: 'ga:date'
        }

        @report = service.get_ga_data(profile_id, start_date, end_date, metrics, dimensions)
    end

end

Solution

  • That was easy but I think I was not putting the token_credential_uri in the call to the API in my tests.

    Observing this answer "How do I refresh my google_oauth2 access token using my refresh token? " I found the right way to do it.

    I just added the following params to the API call: client_id, client_secret, token_credential_uri and refresh_token.

    def analytics_report
        entity_conditions = { entity_type: ContractedProduct.name, entity_id: @contracted_product.id }
        api_integration_refresh_token = ApiIntegration.find_by entity_conditions.merge(key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:refresh_token])
        api_integration_profile_id = ApiIntegration.find_by entity_conditions.merge(key: ApiIntegration::GOOGLE_ANALYTICS_KEYS[:profile_id])
    
        client_info = {
                client_id: Rails.configuration.x.google_api['client_id'],
                client_secret: Rails.configuration.x.google_api['client_secret'],
                token_credential_uri: Rails.configuration.x.google_api['token_credential_uri'],
                refresh_token: api_integration_refresh_token.value
        }
        client = Signet::OAuth2::Client.new(client_info)
        service = Google::Apis::AnalyticsV3::AnalyticsService.new
        service.authorization = client
        profile_id = 'ga:' + api_integration_profile_id.value
        start_date = Date.today.at_beginning_of_month.last_month.to_s
        end_date = Date.today.at_beginning_of_month.last_month.to_s
        metrics = 'ga:pageviews'
        dimensions = {
                dimensions: 'ga:date'
        }
    
        @report = service.get_ga_data(profile_id, start_date, end_date, metrics, dimensions)
    end