Search code examples
ruby-on-railsrubyrest-client

Where should I store the token when calling an API from my Rails app?


I'm writing a library to call a third party API so that it can be used by a Rails app. To authenticate, the API initially uses basic auth to return a token which is used for all other requests. Tokens remain valid for 1 hour, and multiple tokens can be fetched, using the same basic auth credentials, without invalidating any others.

Here's a cut-down version of what I've got so far:

# frozen_string_literal: true

require "rest-client"

class AcmeClient
  ACME_API_URL = Application.secrets.acme[:url]
  ACME_API_KEY = Application.secrets.acme[:key]
  ACME_API_SECRET = Application.secrets.acme[:secret]

  def health_check
    url = ACME_API_URL + "api/health"
    token = fetch_token
    RestClient.get url, { Authorization: "Bearer #{token}"}
  end

  private

  def fetch_token
    url = ACME_API_URL + "/api/token"
    response = RestClient::Request.execute(
      method: :post,
      url: url,
      user: ACME_API_KEY,
      password: ACME_API_SECRET,
      payload: "grant_type=client_credentials"
    )
    JSON.parse(response.body)["access_token"]
  end
end

I've included the health_check method as an example of the API endpoints available.

Having only used existing gems to call APIs before, I'm not sure what to do with the token that gets returned. I don't want to fetch a new one before each API call since that seems unnecessarily excessive, so I'm guessing it would make sense to store it somewhere.

In this case, would it be best to create an acme_tokens database table with token and expires_at columns, and then check the expiry before each new API call?

Or, since calls to the API will be instigated by user actions in the front end of our Rails app, should I store the token in a session variable?

Thanks in advance!


Solution

  • So, i think you can use rails low-level cache to store token. Modify your fetch_token method as below:

      def fetch_token
        Rails.cache.fetch("#{cache_key_with_version}/my_api_token", expires_in: 1.hour) do
          url = ACME_API_URL + "/api/token"
          response = RestClient::Request.execute(
            method: :post,
            url: url,
            user: ACME_API_KEY,
            password: ACME_API_SECRET,
            payload: "grant_type=client_credentials"
          )
          JSON.parse(response.body)["access_token"]
        end
      end
    

    It will return your token while cache is alive, and request new one if cache is expired. Also, you need to configure your cache_store in development/production env.