How to Create an API Key Generator in Rails
This guide will give a step by step guide for creating an API Key generator method in Rails, including a method to create the key and integrating it into a callback.
Guide Tasks
  • Read Tutorial

With our Client validations in place now we can implement an API key generator.

Our API key doesn't have to be overly complex, mainly because microservices typically only need to interact with a limited number of applications. If you are building out an extensive API, such as the Google Maps or Yelp APIs you want need to create an entire application dedicated specifically to creating and managing API keys.

However microservices simply need to ensure that we have some data elements that are too difficult to guess and will be stored securely.

Creating the Generator Spec

Let's begin with creating an expectation for what the feature needs to do. In plain English we can describe it by saying:

  • The API Key generator should create a random string of characters
  • The API Key should be added to the client when created

So we have two requirements, let's create some specs for them. So where should our first spec go? In looking through the options none of them really are a fit for our generator. So let's create a new directory called /features and place it in the /spec folder:

mkdir spec/features

Now let's create a file dedicated to testing the key generation process.

touch spec/features/api_key_spec.rb

Side note: RSpec will run the code in any files in the /spec directory that has a file name ending in _spec.rb. That's why we didn't have to add any configuration code to let it know where to find the files, even though we created a brand new directory.

With that file added let's open it up and add in our expectation:

# spec/features/api_key_spec.rb

require 'rails_helper'

RSpec.describe 'API Key' do
  describe 'creation' do
    it 'can be created' do
      key = ApiKey.generator
      expect(key).to be_kind_of(String)
    end
  end
end

This is a pretty basic test, we're asking it to create a new instance of ApiKey and expect that it returns a String value.

Implementing the Generator

Running the test will give us an error because we don't have an ApiKey class or module in the project. Before we create one, let's discuss a little about OOP design. It would be very easy to load our Client class with this code. However that would break the rule of Single Responsibility. The single responsibility rules states that classes and modules should only have one focus. Our Client class manages the data for clients. If we asked the class to take on the process of generating an API Key it may not seem like a big deal right now. However, if in the future we are asked to extend the functionality of the API Key generator we would need to fill the Client class with code unrelated to the Client directly. This would be a poor OOP design choice.

Instead, let's create a new module that will manage the API Key generation process and store it in the /lib directory.

touch lib/api_key.rb

Let's put the most basic implementation in place to get the test passing, this will include simply hard coding a String value:

# lib/api_key.rb

module ApiKey
  def self.generator
    "I384fHtD1h9XZvs4fGPJUgtt"
  end
end

If you're wondering why it's considered a best practice to process TDD this way it's for a very practical reason. Imagine that We went through the process of building the full module, including the generation of a unique api key. If we ran the test and it failed it would take much longer to debug. The error could have to do with the generator, but it's just as likely due to a spelling mistake or something trivial like that. In this case, by adding in the most basic implementation possible we're able to confirm that we setup the module and method properly.

If you run the test you'll see that it's now passing. Obviously this needs to be refactored since we're simply hard coding the value. Let's add in the code that will create a random sequence of characters so that the method returns a different value each time:

# lib/api_key.rb

module ApiKey
  def self.generator
    SecureRandom.base64.tr('+/=', 'Qrt')
  end
end

If you're wondering about the SecureRandom class I recommend you checking out the documentation. Essentially it's way of generating a random string of 16 characters. We're passing in the base64 method to ensure that it generates various character types, instead of just creating random numbers like the rand method would do.

If you run the tests now you'll see that we're still green, so our refactor worked properly and our new module is returning a string.

large

We could have build a fake module to have a test stub, however for such a basic feature I don't think it's needed. If we're asked to build in more custom behavior at a later time I'd most likely create a stub like we did with the FakeSms module.

Defining the Callback Spec

Looking back at our list of requirements you'll see that this feature has one more item that needs to be built.

  • The API Key should be added to the client when created

This spec will go in the model tests since it's testing behavior directly related to the Client model. We'll create a new describe block called callbacks that will store this type of functionality.

Essentially we want the API Key module to be called each time a new Client is created and to store that value in the database in the api_key column.

Our spec will look something like this:

# spec/models/client_spec.rb

  describe 'callbacks' do
    it 'will have an api_key automatically assigned when created' do
      client = Client.create(source_app: "app_name")
      expect(client.api_key).to_not be_nil
    end
  end

This will create a new Client without an api_key and expect the value to not be nil.

After running the test you'll see that this fails.

large

Implementing the Callback Spec

Callbacks in Rails have developed a bad name. Over the years many developers have abused the tool and caused some messy code bases. However, when used properly, callbacks are a powerful tool that let us access stages of an object lifecycle.

For this feature we'll use the before_validation callback. This will let us set the api_key prior to the validations running. Since we have a validation that requires the api_key to be set upon creation, this process would fail if we don't use the proper callback.

The model should now look like this:

# app/models/client.rb

class Client < ActiveRecord::Base
  has_many :notifications, foreign_key: 'source_app', primary_key: 'source_app'
  validates_presence_of :source_app, :api_key
  validates_uniqueness_of :source_app, :api_key

  before_validation :set_api_key

  private

    def set_api_key
      self.api_key = "I384fHtD1h9XZvs4fGPJUgtt"
    end
end

Once again I'm using the most basic implementation in order to get the test passing. Similar to before I'm taking this course of action to ensure that I'm using the proper callback and code setup. Running the tests now show that they're passing, so that's a good sign.

Now we can slide our call to the ApiKey module instead of the hardcoded string:

# app/models/client.rb

    def set_api_key
      self.api_key = ApiKey.generator
    end

Running the specs will show that everything is passing. Let's test this out in the console to ensure it's working properly.

large

In creating a few test clients you'll see that the system is working properly and generating a different key each time a Client is created, so nice work, this feature is complete!

What's Next?

With the Client class functioning properly we're ready to build in the authentication system that will check each Notification request against the API key and app name parameters to ensure the application cannot be sent requests by unauthorized applications.

Resources