patternrubyrailsMinor
Rails service + OAuth
Viewed 0 times
railsoauthservice
Problem
I'm having trouble structuring the logic of OAuth integration into my web app. In the app, a user creates a
User steps:
My issue is in structuring the code below.
When the user clicks "New Report", they are actually redirected to
Retrieve web properties:
GoogleAnalyticsController
ReportsController
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/oau
Report that consists of data from their Google Analytics account.User steps:
- User clicks 'New Report'
- Google presents the 'Allow Access' page for OAuth access
- User is presented with a list of their GA web properties and selects one
- 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
endReportsController
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
endGoogleAnalytics 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/oau
Solution
The first step would be to refactor out the instanciation of the Oauth2 client:
And then maybe we should devise a strategy to save those pesky oauth tokens:
And we need a controller method to get the access token from the session:
And we need to shove the token into
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:
# 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
endAnd then maybe we should devise a strategy to save those pesky oauth tokens:
class AccessToken ENV['GA_OAUTH_REDIRECT_URL'])
@saved_token = AccessToken.create!(token.to_hash)
session[:oauth_token] = @saved_token.id
redirect_to new_report_path
end
endAnd 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)
endAnd 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
endThen 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
# ...
endCode Snippets
# 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
endclass 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
enddef 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)
endclass GoogleAnalytics
include ActiveModel::Model
attr_writer :token
attr_accessor :user
def initialize(attrs: {})
super
@user ||= Legato::User.new(@token) if @token
end
endclass 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
# ...
endContext
StackExchange Code Review Q#27144, answer score: 2
Revisions (0)
No revisions yet.