Search code examples
ruby-on-railsrubyoauth-2.0omniauthcoinbase-api

Storing an access_token and refresh_token on the user model and using them again causes error


I'm using omniauth-coinbase and coinbase-ruby in my Rails app.

In my app, the user logs in via Coinbase which generates a code which I then turn into an access_token and a refresh_token.

I then create a new OAuth Client under the variable coinbase and it has full functionality.

user_credentials = {
    :access_token => access_token,
    :refresh_token => refresh_token,
    :expires_at => Time.now + 1.day
}
coinbase = Coinbase::OAuthClient.new(ENV['COINBASE_CLIENT_ID'], ENV['COINBASE_CLIENT_SECRET'], user_credentials)

Here's where the problem starts:

I'm storing the access_token and the refresh_token on the User model as strings so that when the user comes back, I can either use two active tokens or use the refresh_token to generate two new tokens, as mentioned in section here "Access Tokens and Refresh Tokens"

Coinbase exposes a refresh! method that I can call on my coinbase OAuth instance which should give me two new tokens.

refresh! works correctly after I generate a new OAuth Client but not when I pull existing tokens (valid or invalid) from the database off of the user model, create a user_credentials hash as noted above, instantiate a new OAuth Client, and then call refresh! on that.

As I understand from my research, the access_token expires. I use a refresh_token whenever to get both a new access_token and a new refresh_token. So in my mind, I should be able to instantiate a new OAuth Client, give it both keys and run a refresh! to put me back in working order and able to make valid calls to the Coinbase API.

Can anyone help out here as to what I may be doing wrong? Thanks!

There error that I get from refresh! is as follows:

OAuth2::Error: invalid_request: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
{"error":"invalid_request","error_description":"The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."}
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/gems/oauth2-0.9.4/lib/oauth2/client.rb:113:in `request'
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/gems/oauth2-0.9.4/lib/oauth2/client.rb:138:in `get_token'
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/gems/oauth2-0.9.4/lib/oauth2/access_token.rb:86:in `refresh!'
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/gems/coinbase-2.0.0/lib/coinbase/oauth_client.rb:58:in `refresh!'
/Users/zackshapiro/dev/stopcoin/lib/tasks/short.rake:14:in `block (3 levels) in <top (required)>'
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:46:in `each'
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:46:in `each'
/Users/zackshapiro/dev/stopcoin/lib/tasks/short.rake:6:in `block (2 levels) in <top (required)>'
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/bin/ruby_executable_hooks:15:in `eval'
/Users/zackshapiro/.rvm/gems/ruby-2.0.0-p451/bin/ruby_executable_hooks:15:in `<main>'

Solution

  • By implementing this on my own, I was both able to reproduce the described exception as well as create working code.

    To reproduce the described exception, I had to use old tokens. This consistently produced a 401 NOT AUTHORIZED HTTP response when attempting to refresh again. This came about on accident by implementing a bug in my own refresh code. Although I had successfully persisted the tokens returned from the OAuth redirect, once I had refreshed to new tokens once, I forgot to persist the new ones back to the model. NOTE: Refreshing the token gives you both a new access_token AND refresh_token back.

    Moving forward, the working code that I ended up with is as follows:

    def test_refresh
      @old_tokens =
        {
          :access_token  => current_user.access_token,
          :refresh_token => current_user.refresh_token
        }
    
      client =
        Coinbase::OAuthClient.new \
          Rails.application.secrets.coinbase_api_key,
          Rails.application.secrets.coinbase_api_secret,
          @old_tokens
    
      new_token = client.refresh!
    
      @new_tokens =
        {
          :access_token  => new_token.token,
          :refresh_token => new_token.refresh_token
        }
    
      current_user.access_token  = @new_tokens[ :access_token ]
      current_user.refresh_token = @new_tokens[ :refresh_token ]
      current_user.save!
    end
    

    FULL WORKING EXAMPLE

    The tokens are being persisted back to the user at the bottom, preventing old/expired tokens from being used.

    Lastly, this may be a duplicate of this question, though it is much less specific.