Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I connect Aweber to my Rails app using OAuth?

I'm trying to integrate my Rails app with Aweber via OAuth, using the official aweber gem.

If I follow their flow in the Rails console, I can get an access token, no problems:

oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
puts oauth.request_token.authorize_url
# => https://auth.aweber.com/1.0/oauth/authorize?oauth_token=xxxxxxxxxxxxxx

Then I visit that URL, type in my credentials, get a verification code, and go back to the rails console:

oauth.authorize_with_verifier 'xxxxxx'
# => #<OAuth::AccessToken>

Success!

The problem is, I want to do this in the real world, not just at the console, which means my Ruby code needs to be broken up into two separate actions. First, there's the controller action which redirects to Aweber's Oauth page:

def aweber
  oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
  redirect_to  oauth.request_token(oauth_callback: "http://127.0.0.1:3000/auth/aweber/callback").authorize_url
end

Then there's the action which gets the access token after the user has input their credentials and been redirected:

def aweber_callback
  oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
  oauth.authorize_with_verifier(params[:oauth_verifier]) 
end

When I do it this way, the final line (authorize_with_verifier) always raises #<OAuth::Unauthorized: 401 Unauthorized>.

Seems like the problem is that I'm initializing the oauth variable twice, meaning I have two unrelated instances of AWeber::Oauth ... and only the instance of AWeber::Oauth that generated the authorize_url can get the access token. But I can't get the same instance in both aweber_callback and aweber because I'm dealing with two completely different threads and instances of the controller.

When I inspect oauth, I can see that the internal variables oauth.request_token.params["oauth_token"] and oauth.request_token.params["oauth_token_secret"] are different in each oauth, which I'm guessing is the cause of the problem. I can get the 'correct' oauth_token from the params (params[:oauth_token]), but I can't figure out how to get the correct oauth_token_secret (not to mention that manually setting instance variables like this feels very hacky and is probably not the best approach.)

How can I generate an access token?

like image 485
GMA Avatar asked May 06 '15 07:05

GMA


2 Answers

Reading through the AWeber API Ruby Library, this bit stands out

What if I don’t want to verify every time?

After verifying once, the oauth object contains an oauth.access_token.token and and oauth.access_token.secret which may be used to authorize your application without having to verify via url:

... oauth.authorize_with_verifier('verification_code') puts 'Access
token: ' + oauth.access_token.token puts 'Access token secret: ' +
oauth.access_token.secret The token and secret can then be saved, and

authorization can be performed as follows:

require 'aweber'
oauth = AWeber::OAuth.new('consumer_key', 'consumer_secret')
#Rather than authorizing with the verification code, we use the token and secret 
oauth.authorize_with_access(YOUR_ACCESS_TOKEN, YOUR_ACCESS_TOKEN_SECRET) 
aweber = AWeber::Base.new(oauth)

So let's run through this:

You can create a class that keeps an object in memory for each User for enough time to finish the sign in and then save the token and secret for use until they expire.

Please note current_user is meant to be anything that uniquely identifies the user. You could use the session ID if your users aren't logged in yet at this point

class AWeberSignIn
    
    def self.start_signing user
        oauth = Rails.cache.fetch("#{user}/aweber", expires_in: 5.minutes) do
            AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
        end
        oauth.request_token(oauth_callback: "http://127.0.0.1:3000/auth/aweber/callback").authorize_url
    end

    def self.authorize_with_verifier user, oauth_verifier
        oauth = Rails.cache.fetch("#{user}/aweber")
        oauth.authorize_with_verifier(oauth_verifier)
        [oauth.access_token.token, oauth.access_token.secret]
    end

    def self.get_base_from_token token, secret
        oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
        oauth.authorize_with_access(token, secret)
        AWeber::Base.new(oauth)
    end
end

With this class, your controller methods become:

def aweber
  redirect_to  AWeberSignIn.start_signin current_user #Assuming you have a current_user helper. Use whatever gives you a unique value per user
end

def aweber_callback
  token, secret = AWeberSignIn.authorize_with_verifier(current_user, params[:oauth_verifier]) 
  #Do something with token and secret. Maybe save it to User attributes?
  #You can then use them to get a AWeber base object via AWeberSignIn.get_base_from_token token, secret
end

Please note that this is using low-level Rails caching. Make sure you set up your caching technique if you want something different from the default

like image 45
Mario Avatar answered Nov 08 '22 00:11

Mario


I finally got this working by storing the oauth_token_secret in the session. (And I have to say, I'm very unimpressed by Aweber's documentation and API setup. This took 10 times longer than it should have.)

Gemfile

gem 'aweber', '~> 1.6.1', require: "aweber"

Routes

get "auth/aweber", to: "integrations#aweber", as: :aweber
get "auth/aweber/callback", to: "integrations#aweber_callback", as: :aweber_callback

Integrations Controller

def aweber
  oauth = get_aweber_oauth

  request_token = oauth.request_token(oauth_callback: aweber_redirect_uri)
  session[:aweber_oauth_token_secret] = request_token.secret

  redirect_to request_token.authorize_url
end


def aweber_callback
  oauth = get_aweber_oauth

  oauth.request_token = OAuth::RequestToken.from_hash(
    oauth.consumer,
    oauth_token: params[:oauth_token],
    oauth_token_secret: session[:aweber_oauth_token_secret],
  )

  access_token = oauth.authorize_with_verifier(params[:oauth_verifier])

  # TODO save access_token.token and access_token.secret
end

private

def get_aweber_oauth
  AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
end

def aweber_redirect_uri
  @_aweber_callback_uri ||= begin
    if Rails.env.production?
      redirect_host = "http://myproductionurl.com"
    else
      redirect_host = "http://127.0.0.1:3000"
    end
    "#{redirect_host}#{Rails.application.routes.url_helpers.aweber_callback_path}"
  end
end

The next step is to store access_token.token and .secret in my DB, then I'll be able to authorize users on future requests like this:

oauth = AWeber::OAuth.new(ENV["AWEBER_CONSUMER_KEY"], ENV["AWEBER_CONSUMER_SECRET"])
oauth.authorize_with_access(current_user.aweber_token, current_user.aweber_secret)
aweber = AWeber::Base.new(oauth)
# Make calls using "aweber"...

I tried using the gem omniauth-aweber in combination with the omniauth gem, but I couldn't get it working (which is a shame, because I'm using other omniauth-xxx gems in this app and it would have been nice to keep things consistent.) Basically, that gem automatically handles the /auth/aweber part of the process, but after it redirects me back to /auth/aweber/callback/ I can't see any way to get the oauth_token_secret - it's not in the request params, the session, or the cookies.

I've answered my own question now but I'll give the bounty to anyone who can come up with an obvious improvement on the above, or figure out a way to make it all work with omniauth-aweber.

like image 50
GMA Avatar answered Nov 07 '22 23:11

GMA