🔓 How to Add Authorization to a Rails API Following TDD
Tags: rails, tdd, security
Published: 2021-11-19 18:40:46

#### Learn how to secure a Rails API with Auth0 by letting you drive by tests.

### <br>

When you're adding authorization to an application, there are two crucial questions:

- Are users that shouldn't have access _actually_ out?
- Are users that should have access _actually_ in?

If you can't answer with certainty both questions, how can you claim to have a secure application? This is something that you can test manually, but a better alternative is test automation. Concretely, I think the best methodology to get there is using Test-Driven Development ([TDD](https://en.wikipedia.org/wiki/Test-driven_development)).

This piece is about adding authorization to a [Ruby on Rails](https://rubyonrails.org/) API by following TDD. You can check out [this article to learn in detail how to secure a Rails API with Auth0](https://auth0.com/blog/secure-rails-api). Also, you can follow this article along with this [repository](https://github.com/auth0-blog/securing-api-rails).

## The TDD Cycle

In its essence, TDD is about a loop with the three steps shown in the following picture:

![TDD loop](https://images.ctfassets.net/23aumh6u8s0i/3FjI42yPKNne2NmL01bPbf/08cd0fca38b916370207e3a16f1c174e/tdd.png)

<p align="center">[[*Source: Thoughtworks*](https://www.thoughtworks.com/insights/blog/test-driven-development-best-thing-has-happened-software-design)]</p>

- First, you make a test for a new feature. Initially, the **test will fail**.
- Then, you write the minimum amount of code that **makes the test pass**.
- Lastly, you **refactor** the code to make the implementation more solid.

Simple, isn't it? It creates a feedback loop where you write code incrementally to fulfill the task at hand. Moreover, it ensures that you build testability, i.e., you write your code so that it can be tested.

To show how to use TDD, let's add authorization to our application step by step, starting with tests. I will use [OAuth](https://auth0.com/docs/protocols/protocol-oauth2) to authorize requests to the API via Auth0. As I explained in [my previous article](https://auth0.com/blog/secure-rails-api), you can <a href="https://auth0.com/signup" data-amp-replace="CLIENT_ID" data-amp-addparams="anonId=CLIENT_ID(cid-scope-cookie-fallback-name)">sign up for a free Auth0 account</a>. Check it out to get [all the details about setting up Auth0](https://auth0.com/blog/secure-rails-api/#Creating-an-API-on-Auth0).

## The Starting Application

Our journey starts with an API bootstrapped with Rails 6. It has three routes that need different levels of protection:

- `/api/messages/public`: Public route.
- `/api/messages/protected`: Requires a valid access token.
- `/api/messages/admin`: Requires a valid access token. Since Auth0 uses JWT as its access token format, we can inspect it and make sure it has a `permissions` claim that contains the scope `read:admin-messages`.

This [branch](https://github.com/auth0-blog/securing-api-rails/tree/starter) is the correct starting point. These are the baseline tests for the three routes:

```ruby
# spec/api/messages_controller_spec.rb
require 'rails_helper'

describe Api::MessagesController, type: :controller do
  describe '#public' do
    subject { get :public, params: { format: :json } }

    it 'returns an accepted answer for the public endpoint' do
      subject

      expect(response).to be_ok

      message = 'The API doesn\'t require an access token to share this message.'
      expect(json_response!).to include('message' => message)
    end
  end

  describe '#protected' do
    subject { get :protected, params: { format: :json } }

    it 'returns an accepted answer for the protected endpoint' do
      subject

      expect(response).to be_ok

      message = 'The API successfully validated your access token.'
      expect(json_response!).to include('message' => message)
    end
  end

  describe '#admin' do
    subject { get :admin, params: { format: :json } }

    it 'returns an accepted answer for the admin endpoint' do
      subject

      expect(response).to be_ok

      message = 'The API successfully recognized you as an admin.'
      expect(json_response!).to include('message' => message)
    end
  end
end
```

We're not enforcing authorization yet. The requests work, but that'll change soon enough.

To launch the tests, move in the project's root folder and run the following command:

```shell
./go test
```

The `go` script allows you to execute different tasks, but we will use it here to run our tests.

For now, we have a pleasant list of green tests, as you can see in the following screenshot:

![Green tests with unprotected endpoints](https://images.ctfassets.net/23aumh6u8s0i/5MNgIu9a2dQIHpWXCf2Lro/58ec0f5a6a59a3a05dd7d8248351b2df/green-tests-unprotected-endpoints.png)

[Read more...](https://auth0.com/blog/authorization-for-rails-api-with-tdd/?utm_source=content_synd&utm_medium=sc&utm_campaign=rails)