Search code examples
ruby-on-railsrubyapiaccess-token

Rails - Get Access Token via URL Request submitting client and secret in Controller


I have a working terminal-command which provides me with an json containing the access token (which is valid only X-minutes): curl -X POST -u [cliend_id]:[secret] "url_to_get_token"

What I want is to generate the Access-Token dynamically in my Controller (and save it for X-minutes in my session - but this is only a plus)

I tried: exec("the terminal command") which shuts down my local server but is in general a bad solution.

Does anyone knows a solution? This should be very basic, but I am a rails newbe.

Thanks a lot in advance!


Solution

  • Since you can get the token calling a URL, what you have to do is to perform that same call but within Rails with any http library, instead of doing an exec call.

    I'm assuming that getting the token is a means to an end, and that end is to perform some action, for example "Get Elements", on a service, which I'll call "My Service". I'm also assuming that "My Service" doesn't have a Ruby client already. If the service you're calling has a Ruby client, use it.

    I'm a defender of Service Objects so I'll propose a solution creating Service Objects that will be called from your controller.

    The high level idea is that there's a MyServiceClient object with all the logic to perform actions to your service, including getting the token. There's also a model, TokenStorage, responsible of only storing and validating tokens against the database. Then, SomeController uses MyServiceClient and TokenStorage to perform and validate actions against your service.

    A separation like this keeps objects very small and doesn't pollute neither your controller nor your model with token rotation logic or intrinsic details about "My Service".

    # Gemfile
    
    gem 'http'
    
    # app/controllers/some_controller.rb
    
    class SomeController < ActionController::Base
      def index
        @elements = my_service_client.get_elements
        # now the view will have access to the elements from your service
      end
    
      private
    
      def my_service_client
        @_my_service_client ||= MyServiceClient.new(TokenStorage)
      end
    end
    
    # app/models/token_storage.rb
    
    class TokenStorage < ActiveRecord::Base
      def self.valid?
        expiration = select(:expiration).order(expiration: :desc).first
        expiration && Time.now < expiration
      end
    
      def self.token
        select(:token).order(expiration: :desc).first
      end
    
      def self.store(token, expiration)
        create(token: token, expiration: expiration)
      end
    end
    
    # lib/my_service_client.rb
    
    require 'http'
    
    class MyServiceClient
      def initialize(token_storage)
        @token_storage = token_storage
      end
    
      def get_elements
        HTTP.auth(token)
          .get(Config.my_service_url_to_get_elements)
      end
    
      private
    
      attr_reader :token_storage
    
      def token
        if token_storage.valid?
          token_storage.token
        else
          rotate_token
        end
      end
    
      def rotate_token
        token, expiration = create_token
        token_storage.store(token, expiration)
        token
      end
    
      def create_token
        parse_response(get_token_from_service)
      end
    
      def get_token_from_service
        # Try to store client_id and secret in environment variables rather than in
        # the codebase.
        HTTP.basic_auth(user: Config.my_service_client_id,
                        pass: Config.my_service_secret)
          .post(Config.my_service_url_to_get_token)
      end
    
      def parse_response(response)
        # Here you parse the response according to your interface, and get the token
        # value `token` and expiration date `expiration`.
        [token, expiration]
      end
    end