developers

Building Secure APIs with Rails 6 and Auth0

Learn how to easily integrate Auth0 with Rails 6 to build secure APIs.

Jun 15, 202012 min read

TL;DR: In this tutorial, you'll learn how to use Rails 6 and Auth0 to build an API with both authorized and open endpoints.

The ecosystem for building backend services feels more crowded than ever before. With a multitude of choices for building APIs and backend services — from Node.js frameworks and web applications to lightweight serverless functions — it's sometimes easy to overlook longstanding, tried-and-true frameworks like Ruby on Rails, which (as of 2020) is now 15 years old!

Rails sometimes gets a bad rap for not being the newest or shiniest framework out there. But Rails continues to provide sensible defaults, the ability to quickly generate scaffolding for your web apps and APIs, and constant iterative progress of new features. With Rails 6, that includes easy management of multi-environment credentials, the new Zeitwork autoloader, and many other features beyond the scope of this tutorial.

In this tutorial, you'll see how straightforward it is to spin up a new Rails API, add JSON Web Token (JWT) support to make authorized requests and secure your endpoints, and leverage Auth0 as your authentication system to verify users and access tokens. The API you'll build is a simple one: you'll create an endpoint for reading, creating, and deleting short messages called Chirps — think of it as your own private Twitter API.

With Rails 6 and Auth0, it's straightforward to spin up an API and secure your endpoints.

Tweet This

You can find all code from this tutorial in this GitHub repository.

Read on to get started!

Prerequisites

  • Rails and prerequisites. You'll need to install Rails itself before you can use the Rails CLI. If you already have Ruby installed, you can install Rails like so:
    gem install rails
    . You'll also need Rails' prerequisites: Ruby and SQLite3. You can find full instructions on installing Rails on the Ruby on Rails guide site
  • An Auth0 account. If you don't have one, you can sign up for a free Auth0 account here.

Try out the most powerful authentication platform for free.

Get started →

Create a New Auth0 API

Before you jump into code, you'll need to create a new Auth0 API. Head to the API section of your Auth0 dashboard and click the orange "Create API" button. Fill out the form with the following values:

  • Name: Rails Secure API
  • Identifier: https://rails-secure-api
  • Signing Algorithm: RS256 (this should be the default selection)

Creating a new Auth0 API

Copy the

identifier
value (
https://rails-secure-api
) — you'll need it when setting up your Rails app. You'll also need to grab your Auth0 Domain. Unless you're using a custom domain, this value will be
[TENANT_NAME].auth0.com
. If you're unsure what this value is, open the "Test" tab in your API's settings and look at the
url
argument in the code sample under "Asking Auth0 for tokens from my application":

finding Auth0 Domain in test section of API

Once you've finished creating your API, you can head to the command line and start building your Rails application!

Scaffolding

The most straightforward way to generate a new Rails app is by using the CLI. Run the following command in your terminal:

$ rails new auth0_rails_api --api

Once the Rails CLI finishes, you'll have a fully operational Rails application ready to go. To ensure this, run the following in your command line:

$ cd auth0_rails_api
$ bundle exec rails s

Navigate to http://localhost:3000 in your browser; you should see a standard "greeting" screen indicating that the Rails app is successfully running:

Rails greeting screen

Now that you're up and running, it's time to start building your API. Stop the running Rails process (

ctrl + c
) and read on!

Create Authorization Handlers

In this section, you'll build a class for handling JSON Web Token (JWT) verification, plus a service that uses the JWT class to perform authorization when new requests are sent to your API.

First, you'll need to add the

jwt
gem to your application. Open
Gemfile
in your project's root directory and add the following line to the file:

gem 'jwt'

Run

bundle install
from the command line to install the gem.

Next, you need to add the API identifier and domain values from your Auth0 configuration into your Rails environment.

In your command line, run the following:

$ bundle exec rails credentials:edit --environment development

Tip: If you don't have a default text editor set up in your command line, you may need to pass an

EDITOR
argument with the command, e.g.,
EDITOR="vim" bundle exec rails credentials:edit --environment development

If you've used Rails 5, you're probably familiar with the introduction of encrypted credentials in Rails 5.2. The reason you're passing

--environment development
into the argument is a Rails 6 development — credentials now support multiple environments!

When the credentials file opens, delete any existing contents and add the code snippet below. Be sure to replace

{AUTH0_DOMAIN}
with the Auth0 Domain value you copied down in the "Create a New Auth0 API" section. If you used a custom
identifier
value in that section, you'll also need to replace the
api_identifier
value.

auth0:
  api_identifier: https://rails-secure-api
  domain: {AUTH0_DOMAIN}

After you've saved the credentials file, open your project directory in your code editor of choice. Create a directory under

app
called
lib
and create a new file called
json_web_token.rb
.

Tip: The reason you're creating the new class in

app/lib
rather than
lib
is that subdirectories under
app
are autoloaded by default. If you're not familiar with Rails 6 autoloading with the Zeitwork engine, the Rails guide to autoloading or this article on understanding Zeitwork in Rails 6 can provide more information.

Add the following to

json_web_token.rb
:

# app/lib/json_web_token.rb
require 'net/http'
require 'uri'

class JsonWebToken
  def self.verify(token)
    JWT.decode(token, nil,
               true, # Verify the signature of this token
               algorithm: 'RS256',
               iss: "https://#{Rails.application.credentials.auth0[:domain]}/",
               verify_iss: true,
               aud: Rails.application.credentials.auth0[:api_identifier],
               verify_aud: true) do |header|
      jwks_hash[header['kid']]
    end
  end

  def self.jwks_hash
    jwks_raw = Net::HTTP.get URI("https://#{Rails.application.credentials.auth0[:domain]}/.well-known/jwks.json")
    jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
    Hash[
      jwks_keys
      .map do |k|
        [
          k['kid'],
          OpenSSL::X509::Certificate.new(
            Base64.decode64(k['x5c'].first)
          ).public_key
        ]
      end
    ]
  end
end

This class is taken pretty much verbatim from the Auth0 Rails API QuickStart. It performs the actual verification of the JWT-formatted access token that API requests will provide.

Next, create a subdirectory under

app
called
services
. Create a new file called
authorization_service.rb
and add the following to it:

class AuthorizationService

  def initialize(headers = {})
    @headers = headers
  end

  def authenticate_request!
    verify_token
  end

  private

  def http_token
    if @headers['Authorization'].present?
      @headers['Authorization'].split(' ').last
    end
  end

  def verify_token
    JsonWebToken.verify(http_token)
  end

end

By extracting the authorization logic to a service, you're separating the concerns of verifying the authenticity of a JWT versus authorizing an HTTP request. The

AuthorizationService
grabs the access token in the
Authorization
HTTP Header and passes it to the
JsonWebToken
for verification.

With this separation of responsibility, you can limit any possible future changes to the JWT verification process to the

JsonWebToken
class. With your
AuthorizationService
in place, you can now easily add an authorization check to your controllers — and you'll do just that in the next section!

Securing an API Endpoint

Build a base SecuredController

At this point, you could start creating controllers and adding an authorization check to each controller individually. However, if you're planning to secure your entire API, it's easier to create a base controller from which your secured controllers can inherit the authorization check.

Create a new file called

secured_controller.rb
in
apps/controllers
and add the following to it:

class SecuredController < ApplicationController
  before_action :authorize_request

  private

  def authorize_request
    AuthorizationService.new(request.headers).authenticate_request!
  rescue JWT::VerificationError, JWT::DecodeError
    render json: { errors: ['Not Authenticated'] }, status: :unauthorized
  end
end

The

SecuredController
inherits from
ApplicationController
and adds a
before_action
call, ensuring that every request will pass through the
AuthorizationService
before running any additional handlers.
SecuredController
also handles returning a
401 Unauthorized
response if the request doesn't pass muster.

One more thing: you'll want to disable Parameter Wrapping in our API, for simplicity's sake. Open

config/initializers/wrap_parameters.rb
and change the uncommented code to read as follows:

# ...
ActiveSupport.on_load(:action_controller) do
  wrap_parameters format: []
end

And with that, your scaffolding is complete, and you're ready to create a real live secured API endpoint!

Build an API endpoint

The Rails CLI can be used to create the various moving pieces you need for an API endpoint. Head to your command line again and run the following:

$ bundle exec rails generate resource chirp body:text published:boolean
$ bundle exec rails db:migrate

The

resource
generator is a quick way to create a model, controller, and route (plus unit tests) for your new
Chirp
data type. Running
rails db:migrate
afterward creates the necessary tables in your database.

With your scaffolding complete, it's time to write the code to handle your endpoint!

First, open up

app/models/chirp.rb
and add the following to the model class:

class Chirp < ApplicationRecord
  # 👇 Add this line 👇
  validates_presence_of :body, :published
end

This line will validate the

body
and
published
fields when a new Chirp is created and automatically return a
422
response code when a
POST
request without those fields is received.

Next, open

app/controllers/chirps_controller.rb
and replace the contents with the following:

class ChirpsController < SecuredController
  def index
    chirps = Chirp.all
    render json: chirps
  end

  def show
    chirp = Chirp.find(params[:id])
    render json: chirp
  rescue ActiveRecord::RecordNotFound
    head :not_found
  end

  def create
    chirp = Chirp.create!(chirp_params)
    render json: chirp, status: :created
  end

  def destroy
    chirp = Chirp.find(params[:id])
    chirp.delete
    head :no_content
  end

  private

  def chirp_params
    params.permit(:body, :published)
  end
end

If you've worked with Rails before, this should look pretty straightforward. The controller supports

GET
requests (both of specific Chirps and all Chirps),
POST
s to create new Chirps, and
DELETE
s to destroy specific Chirps.

It's important to note that the code sample changes the controller's inheritance chain. The generated controller inherits from

ApplicationController
, whereas the above code inherits from
SecuredController
, so all requests must include an access token.

Allowing unauthenticated requests

Your API endpoint should now be working, but there's one issue: much like Twitter, you might want your Chirps to be publicly viewable! For that to work, unauthenticated

GET
requests need to be allowed, but
POST
and
DELETE
requests should still require an access token.

Luckily, allowing this is straightforward. Add the following line to the top of your

ChirpsController
class:

class ChirpsController < SecuredController
  skip_before_action :authorize_request, only: [:index, :show]
  # 👆 New Code 👆

  # 👇 Existing Code 👇

With

skip_before_action
, individual controllers like
ChirpsController
can easily override the default
SecuredController
behavior as needed.

And with that, your API is ready to go!

Test it out

To test your API, head back to the API page of your Auth0 Dashboard and click on "Rails Secure API" to view the API details. Click the "Test" tab and scroll down to the "Asking Auth0 for tokens from my application" section. Under the "Response" heading, you can copy a test access token to use in requests to your API.

find access token on Auth0 API Details page

Access token in metaphorical hand, it's time to make a request. In your command line, start the Rails server:

$ bundle exec rails server

(The request examples here use cURL, but Postman or any number of other tools for crafting HTTP requests will work.)

First, request the

chirps
endpoint without your access token to ensure unauthenticated GET requests work:

$ curl http://localhost:3000/chirps

You should see the following response:

[]

Your request is working, but since no Chirps exist in the database, an empty array comes back.

Next, make an unauthenticated

POST
request to ensure your
SecuredController
implementation is correct:

$ curl -H "Content-Type: application/json" -d '{"body":"this is my first chirp!", "published":true}' -X POST http://localhost:3000/chirps

You should see the following response:

{"errors":["Not Authenticated"]}

Success! Without an access token, you can't create a chirp.

Let's successfully create a Chirp; To do so, you'll need to add the

Authorization
header to your request. Replace
[ACCESS_TOKEN]
with the token from your Auth0 dashboard and run the following command:

$ curl -H "Content-Type: application/json" -H "Authorization: bearer [ACCESS_TOKEN]" -d '{"body":"this is my first chirp!", "published":true}' -X POST http://localhost:3000/chirps

And the response:

{"id":1,"body":"this is my first chirp!","published":true,"created_at":"2020-06-08T21:13:03.949Z","updated_at":"2020-06-08T21:13:03.949Z"}

(Timestamps may vary, obviously!)

You've successfully made a secure request to your API and created your first Chirp!

You can now request that record without needing an access token:

$ curl http://localhost:3000/chirps/1

To delete the record, you'll need to provide the access token once again:

$ curl -i -H "Authorization: bearer [ACCESS_TOKEN]" -X DELETE http://localhost:3000/chirps/1

The

-i
argument is provided to view the full HTTP response, including headers. You should see a response similar to the following:

HTTP/1.1 204 No Content
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-cache
X-Request-Id: c44a6a80-74d5-40ab-a03a-883714f41d32
X-Runtime: 0.125480

A

204
response is what you specified in the controller on a successful delete, which means it worked. Your API is now fully operational, and you're on your way to creating your own miniature Twitter!

Conclusion

Rails is an excellent choice for spinning up APIs and using the many tools it provides to build and secure your endpoints. While well-known gems like devise exist to build your own token authentication solution, it adds complexity and overhead to building and maintaining your API. Using Auth0 for authentication and credential handling, social login, and features like role-based access control and custom user metadata, you can keep the complexity of user management separated from your authorization and business logic in your codebase.

Some next steps to take could be to continue building the functionality of your API or start exploring Auth0's QuickStarts for JavaScript single-page apps to create a front-end for your API. Front-end apps can perform the initial authentication with Auth0, providing you with the access token you need to communicate with your API.

The repository for the complete application described in this tutorial can be found on GitHub.

Happy building!