5
\$\begingroup\$

I'm having trouble structuring the logic of OAuth integration into my web app. In the app, a user creates a Report that consists of data from their Google Analytics account.

User steps:

  1. User clicks 'New Report'
  2. Google presents the 'Allow Access' page for OAuth access
  3. User is presented with a list of their GA web properties and selects one
  4. A report is created using data from the selected web property

My issue is in structuring the code below.

When the user clicks "New Report", they are actually redirected to google_analytics#ga_session to begin the authorization process. The code to retrieve the user's web properties succeeds, but the code at the bottom needs to be refactored so it is reusable when retrieving web property data. The main two issues I can't figure out is how to make the Google Analytics instance reusable and how to structure the OAuth redirects.

Retrieve web properties:

GoogleAnalyticsController

def ga_session
 client = OAuth2::Client.new(ENV['GA_CLIENT_ID'], ENV['GA_SECRET_KEY'], {
 :authorize_url => 'https://accounts.google.com/o/oauth2/auth',
 :token_url => 'https://accounts.google.com/o/oauth2/token'
 })
 redirect_to client.auth_code.authorize_url({
 :scope => 'https://www.googleapis.com/auth/analytics.readonly',
 :redirect_uri => ENV['GA_OAUTH_REDIRECT_URL'],
 :access_type => 'offline'
 })
 end
 def oauth_callback
 session[:oauth_code] = params[:code]
 redirect_to new_report_path
 end

ReportsController

def new
 @report = Report.new
 ga_obj = GoogleAnalytics.new
 ga_obj.initialize_ga_session(session[:oauth_code])
 @ga_web_properties = ga_obj.fetch_web_properties
end

GoogleAnalytics model

def initialize_ga_session(oauth_code)
 client = OAuth2::Client.new(ENV['GA_CLIENT_ID'], ENV['GA_SECRET_KEY'], {
 :authorize_url => 'https://accounts.google.com/o/oauth2/auth',
 :token_url => 'https://accounts.google.com/o/oauth2/token'
 })
 access_token_obj = client.auth_code.get_token(oauth_code, :redirect_uri => ENV['GA_OAUTH_REDIRECT_URL'])
 self.user = Legato::User.new(access_token_obj)
 end
 def fetch_web_properties
 self.user.web_properties
 end

Retrieve web property data: when creating the report

ReportsController

def create
 @report = Report.new(params[:report])
 @report.get_top_traffic_keywords(session[:oauth_code])
 create!
end

Report Model

def get_keywords(oauth_code)
 ga = GoogleAnalytics.new
 ga.initialize_ga_session(oauth_code) # this is a problem b/c the user will be redirected the new_report_path after the callack
 self.url = ga.fetch_url(self.web_property_id)
 self.keywords = # Get keywords for self.url from another service
 keyword_revenue_data(oauth_code)
end
def keyword_revenue_data(oauth_code)
 ga = GoogleAnalytics.new
 ga.initialize_ga_session(oauth_code)
 revenue_data = # Get revenue data
end
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jun 7, 2013 at 14:17
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

The first step would be to refactor out the instanciation of the Oauth2 client:

# lib/analytics_oauth2_client
class AnalyticsOAuth2Client > OAuth2::Client
 def initialize(client_id, client_secret, options = {}, &block)
 client_id || = ENV['GA_CLIENT_ID']
 client_secret || = ENV['GA_SECRET_KEY']
 options.merge!(
 authorize_url: 'https://accounts.google.com/o/oauth2/auth',
 token_url: 'https://accounts.google.com/o/oauth2/token'
 ) 
 super(client_id, client_secret, options, &block)
 end 
end

And then maybe we should devise a strategy to save those pesky oauth tokens:

class AccessToken < ActiveRecord::Base
 # @expires_at [Int]
 # @access_token [String]
 # @refresh_token [String]
end
class GoogleAnalyticsController
 # ...
 def oauth_callback
 @client = AnalyticsOAuth2Client.new
 token = @client.auth_code.get_token(params[:code], :redirect_uri => ENV['GA_OAUTH_REDIRECT_URL'])
 @saved_token = AccessToken.create!(token.to_hash)
 session[:oauth_token] = @saved_token.id
 redirect_to new_report_path
 end
end

And we need a controller method to get the access token from the session:

def authorize!
 # ... @todo redirect to back to authentication if no `session[:oauth_token]`
 stored_token = Token.find!(session[:oauth_token])
 @token = OAuth2::AccessToken(AnalyticsOAuth2Client.new, stored_token.attributes)
 @client = GoogleAnalytics.new(token: @token)
end

And we need to shove the token into GoogleAnalytics:

class GoogleAnalytics
 include ActiveModel::Model
 attr_writer :token
 attr_accessor :user
 def initialize(attrs: {})
 super
 @user ||= Legato::User.new(@token) if @token
 end
end

Then we consider the separation of concerns - models should not deal with authentication - thats a controller concern. You should pass whatever authorized object the models needs from the controller. Dependency injection is commonly used for this:

class Report
 attr_writer :client
 def get_keywords(oauth_code)
 self.url = @client.fetch_url(self.web_property_id)
 self.keywords = # Get keywords for self.url from another service
 keyword_revenue_data(oauth_code)
 end
end
class ReportsController
 # ...
 def create
 @report = Report.new(client: @client, params[:report])
 @report.get_top_traffic_keywords
 create!
 end
 # ...
end
answered May 8, 2015 at 14:09
\$\endgroup\$
2
  • \$\begingroup\$ Disclaimer: I have never done OAuth with google, this is general advice from doing other OAuth projects. Its not guaranteed to be complete or even runnable but rather point in the correct direction. \$\endgroup\$ Commented May 8, 2015 at 14:11
  • \$\begingroup\$ I would also use OmniAuth with omniauth-google-oauth2 instead of reinventing the wheel. \$\endgroup\$ Commented May 8, 2015 at 14:16

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.