How to build secure APIs with Ruby on Rails: Security guide

How to build secure APIs with Ruby on Rails: Security guide

Are your Ruby on Rails APIs as secure as they could be? In today's world, where digital security is no longer an option, ensuring the robustness of your APIs is crucial. If you find yourself uncertain when attempting to answer this question, then this guide is for you.

In this guide, we will be exploring how you can build a secure API on Ruby on Rails that prioritizes the safety and security of your users' data. Whether you are a seasoned developer or just starting out on your coding journey, we've got you covered.

Throughout this guide, we will present the essential steps required to create a secure API using Ruby on Rails. Don't worry if you're unfamiliar with the Ruby on Rails framework – we'll explain everything in simple terms. Together, we will explore various techniques and best practices that will empower you to build a robust and secure API.

By the end of this article, you will be equipped with valuable knowledge on how to authenticate and authorize your users, protect against common security vulnerabilities such as cross-site scripting (XSS) and cross-site request forgery (CSRF), handle data input and output securely, and much more.

So, if you're ready to strengthen your API's security and provide your users with peace of mind, let's dive into the exciting world of building a secure API with Ruby on Rails!

Building a secure API in Ruby on Rails: The basics

How to start building an API with Ruby on Rails?

Setting up your Rails application

Let's start by creating a new Ruby on Rails application. Open your terminal and run the following command:

rails new secure_api

This command creates a new Rails application named "secure_api."

Creating API endpoints

We are going to create a simple API with a single endpoint to manage "todos." Let's generate a scaffold for the "Todo" resource:

rails generate scaffold Todo title:string completed:boolean

This command generates a controller, model, and views for the "Todo" resource. We won't be using views for our API, but Rails creates them by default.

Configuring routes

Next, open the config/routes.rb file and configure the routes for your API:

# config/routes.rb

Rails.application.routes.draw do
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      resources :todos
    end
  end
end

This configuration sets up API endpoints under the /api/v1 namespace.

Implementing basic authentication

Authentication ensures that only authorized users can access your API. In this example, we'll use the Devise gem for user authentication.

First, add Devise to your Gemfile and run bundle install:

gem 'devise'

Now, install Devise and generate the User model:

rails generate devise:install
rails generate devise User

Implementing basic authorization

Authorization defines what actions users can perform in your API. We'll use the CanCanCan gem for role-based authorization.

Add CanCanCan to your Gemfile and run bundle install:

gem 'cancancan'

Generate an Ability model for authorization rules:

rails generate cancan:ability

Define roles and authorization rules in the ability.rb file:

# app/models/ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    if user.admin?
      can :manage, :all
    else
      can :read, Todo
      can :create, Todo
      can :update, Todo, user_id: user.id
      can :destroy, Todo, user_id: user.id
    end
  end
end

Validating and sanitizing input

Input validation and sanitization are crucial for security. FastAPI, a Python framework, is known for this, but in Rails, we use standard Rails validation methods. Let's validate our Todo model.

Open the app/models/todo.rb file and add validation rules:

# app/models/todo.rb

class Todo < ApplicationRecord
  validates :title, presence: true
  validates :completed, inclusion: { in: [true, false] }
end

Testing your secure API

Now that we've built our secure Ruby on Rails API with authentication, authorization, and input validation, it's time to test it.

Start your Rails server:

rails server

You can use tools like curl or Postman to test your API endpoints. For example, to create a new todo:

curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy groceries", "completed": false}' http://localhost:3000/api/v1/todos

Make sure to test different scenarios, including authentication, authorization, and input validation, to ensure your API behaves securely and as expected.

Why does API security matter?

Building a secure API is essential to protect your organization against data breaches, ensure the security of sensitive data, and stay compliant with security regulations.

With the increasing popularity of web and mobile applications, API security has become a top concern for developers. Ruby on Rails provides a robust and secure environment for developing APIs, with built-in features and libraries that help mitigate common security risks. By following best practices and implementing the right security measures, we can ensure that our API is protected against potential attacks and vulnerabilities.

Understanding the basics of API security

To build a secure API in Ruby on Rails, it is important to have a solid understanding of the basics of API security.

API security refers to the measures taken to protect the API from unauthorized access, data breaches, and other security threats.

đŸ’¡
Have multiple API types within your organization? Check out the following article: How to avoid data breaches with GraphQL.

One fundamental aspect of API security is authentication, which ensures that only authenticated users or applications can access the API. In Ruby on Rails, you can implement authentication using gems like Devise or JWT (Json Web Tokens).

Another important aspect is authorization, which determines what actions a particular user or application is allowed to perform within the API. This can be achieved through role-based access control or by using authorization frameworks like Pundit or CanCanCan.

Let's see how you can do this :

Install the required gems in your Rails application:

# Gemfile

gem 'devise'
gem 'cancancan'

Then, run bundle install to install the gems.

Set up Devise for authentication. Run the following command to generate a User model and configure Devise:

rails generate devise:install
rails generate devise User

Define roles for your application. You can use an enum in your User model to specify roles:

# app/models/user.rb

class User < ApplicationRecord
  enum role: [:user, :admin]
  # ...
end

Configure CanCanCan for authorization:

# Ability.rb
class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)

    if user.admin?
      can :manage, :all
    else
      can :read, :all
    end
  end
end

Finally, apply authorization checks in your controllers:

# app/controllers/api/v1/posts_controller.rb

class Api::V1::PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]
  load_and_authorize_resource

  # ...

  private

  def set_post
    @post = Post.find(params[:id])
  end
end

Additionally, it is crucial to validate and sanitize input data to prevent common security vulnerabilities such as SQL injection or cross-site scripting (XSS) attacks.

Implementing authentication in Ruby on Rails

Authentication is a crucial aspect of building a secure API in Ruby on Rails. It allows users to verify their identity and grants them access to protected resources. With Rails, implementing authentication can be made easy using gems like Devise or Sorcery.

These gems provide pre-built authentication functionality, saving us time and effort. Let's take a look at an example of how to use Devise to authenticate users. First, we need to install the Devise gem by adding it to our Gemfile and running the bundle install command.

# Gemfile
# Add Devise gem to your Rails application
gem 'devise'

# Terminal
# Run bundle install to install the gem
bundle install

Now, let us create a User model and set up authentication using Devise:

# Generate a User model with Devise
rails generate devise User

# Run the database migration
rails db:migrate

Next, you can illustrate how to protect your API endpoints with authentication using Devise. In your controllers, you can use the before_action filter to ensure that only authenticated users can access specific resources:

# app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
  before_action :authenticate_user!

  def index
    posts = Post.all
    render json: posts
  end

  def create
    # Create a new post
  end

  # Other actions...

end

To add authentication to a specific model, such as User, we can run the rails generate devise MODEL_NAME command. Finally, we need to configure our routes to handle authentication paths by adding the following line to our config/routes.rb file: devise_for: users. And that's it! With just a few simple steps, we can implement authentication in our Ruby on Rails API using Devise.

Token-based authentication

To ensure that only authorized users have access to certain resources you can use token-based authentication, where users receive a token upon successful login and this token is then used for subsequent API requests.

To achieve this, add the following gem to the gemfile and run bundle install :

# Gemfile
gem 'devise_token_auth'

Configure Devise for token-based authentication:

# config/initializers/devise_token_auth.rb

Devise.setup do |config|
  config.secret_key = 'your_secret_key' # Replace with a secure secret key
end

Create a user model with token authentication:

generate devise_token_auth:install User auth

Migrate the database:

rails db:migrate

Protect your API routes with authentication using before_action:

# app/controllers/api/v1/base_controller.rb

module Api
  module V1
    class BaseController < ApplicationController
      before_action :authenticate_user!

      # Your API actions go here
    end
  end
end

Multiple tokens authentication with Tiddle

Install Tiddle to get started, add Tiddle to your Gemfile and run bundle install:

# Gemfile
gem 'tiddle'

Then run bundle install to install the gem

Configure Tiddle Next, configure Tiddle by creating an initializer. Run the following command to generate the initializer file:

rails generate tiddle:install

This will create a file named config/initializers/tiddle.rb. Open this file and configure it according to your needs. You can set options like token expiration time and the name of the user model. For example:

Tiddle.configure do |config|
  config.token_lifetime = 1.week
  config.user_identifier = :email
end

Update User Model Tiddle requires your User model to have some specific methods. In your User model (e.g., app/models/user.rb), include the Tiddle::TokenIssuer module and define the required methods:

class User < ApplicationRecord
  include Tiddle::TokenIssuer

  # Define the following methods:
  def self.find_by_token(token)
    # Implement logic to find a user by token
  end

  def self.build_from_token(token)
    # Implement logic to build a user from a token
  end
end

Implement Authentication in Your Controller Now, let's implement authentication in your API controllers. Here's an example of how to protect a specific endpoint:

class ApiController < ApplicationController
  before_action :authenticate_user!

  def secure_endpoint
    # Your secure endpoint logic goes here
  end
end

Generate and Validate Tokens To generate and validate tokens for authentication, you can use Tiddle's Tiddle.create_and_return_token method in your login action and the Tiddle.expire_token method to log users out. Here's an example of a login action:

class SessionsController < ApplicationController
  def create
    user = User.find_by(email: params[:email])
    
    if user&.authenticate(params[:password])
      token = Tiddle.create_and_return_token(user, request)
      render json: { auth_token: token }
    else
      render json: { error: 'Invalid credentials' }, status: :unauthorized
    end
  end

  def destroy
    Tiddle.expire_token(current_user, request)
    head :no_content
  end
end

Finally, test your API to ensure everything is working as expected. You should be able to authenticate users and secure your API endpoints using the generated tokens.

Handling permissions and authorization

To ensure that only authorized users have access to specific parts of your API, it is essential to handle permissions and authorization effectively. In Ruby on Rails, one popular approach is to use a gem called "CanCanCan." This gem allows you to define abilities and permissions for each user role within your application. By setting up abilities, you can easily control what actions each user can perform on different resources. For example, you can define that only administrators have the ability to delete records, while regular users can only view or edit them. You can implement this by creating an ability file where you define the abilities for each role using straightforward code snippets.

Here is how you would implement it :

Add the CanCanCan gem to your Gemfile and run bundle install to install it:

# Gemfile
gem 'cancancan'

Generate a User model and a Post model (or any resource you want to protect) in your Rails application:

rails generate model User name:string role:string
rails generate model Post title:string content:text user:references
rails db:migrate

Define user roles and abilities in the Ability model using "cancancan." Create an ability.rb file in the app/models directory:

# app/models/ability.rb
class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # Guest user (not logged in)

    if user.admin?
      can :manage, :all # Admins can manage all resources
    else
      can :read, Post # Regular users can read posts
      can :create, Post # Regular users can create posts
      can :update, Post, user_id: user.id # Regular users can update their own posts
    end
  end
end

In your controller, use CanCanCan to authorize actions based on user roles:

# app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
  load_and_authorize_resource # Loads the Post resource and authorizes actions

  def index
    render json: @posts
  end

  def create
    # Create a new post
    if @post.save
      render json: @post, status: :created
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  def update
    # Update an existing post
    if @post.update(post_params)
      render json: @post
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  # Other actions...

  private

  def post_params
    params.require(:post).permit(:title, :content)
  end
end

Protecting sensitive data

Encrypting sensitive data

In order to build a secure API in Ruby on Rails, it is crucial to protect sensitive data. One way to achieve this is by implementing proper authentication and authorization mechanisms. By requiring users to authenticate themselves before accessing certain data or performing specific actions, we can ensure that only authorized individuals have access to sensitive information. This can be achieved by using a tool like Devise, mentioned previously, which provides a simple and flexible authentication solution. Additionally, it is important to encrypt sensitive data, such as passwords, using secure hashing algorithms like bcrypt. Storing passwords in plain text is a big no-no, as it leaves them vulnerable to theft and misuse. By hashing passwords, even if an attacker gains access to the stored data, they will not be able to reverse-engineer the original passwords. Here's an example of how to use bcrypt to securely store and authenticate passwords:

# Gemfile
gem 'bcrypt'

# app/models/user.rb
class User < ApplicationRecord
  has_secure_password
end

# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    user = User.find_by(email: params[:email])
    if user&.authenticate(params[:password])
      # Successful authentication
    else
      # Invalid credentials
    end
  end
end

By implementing these measures, we can better protect sensitive data in our Ruby on Rails API and ensure that only authorized users have access to it.

Preventing API abuse and attacks

One essential aspect of building a secure API in Ruby on Rails is taking measures to prevent potential abuse and attacks. One common method to achieve this is by implementing rate limiting. By setting limits on the number of requests a user or an IP address can make within a specific time frame, we can minimize the risk of brute force attacks or overwhelming the server with excessive traffic. Here's a snippet of code that demonstrates how rate limiting can be implemented in Ruby on Rails using the 'rack-attack' gem:

# config/initializers/rack_attack.rb

Rack::Attack.throttle('requests per IP', limit: 100, period: 1.minute) do |request|
  request.ip
end

In the example above, we define a throttle rule named 'requests per IP.' It restricts the number of requests an IP address can make to 100 requests per minute. This simple measure helps protect our API by preventing potential abuse from individual IP addresses.

Securing your production environment with a least privilege CORS policy

When deploying a web application to production, one of the essential aspects to consider is Cross-Origin Resource Sharing (CORS). CORS is a security feature that controls which domains can access your application's resources. It's crucial to establish a CORS policy that follows the principle of least privilege to minimize potential security risks. Fortunately, the rack-cors gem in Ruby on Rails provides a powerful tool to help you achieve this.

In this section, we'll discuss how to set up a CORS policy in your Ruby on Rails application's production environment using the rack-cors gem. We'll emphasize the principle of least privilege to ensure your application's security.

What is the principle of least privilege?

The principle of least privilege is a fundamental security concept that restricts access rights for users and applications to the minimum permissions required to perform their tasks. In the context of CORS, it means allowing only specific, necessary domains to access your resources and blocking all other origins.

Setting up CORS policy with rack-cors

Here are the steps to establish a CORS policy that follows the principle of least privilege using the rack-cors gem:

Install rack-cors Gem

First, you need to include the rack-cors gem in your Rails project's Gemfile and run bundle install to install it:

gem 'rack-cors'

Configure CORS in config/application.rb

In your Rails application, you can define CORS policies in the config/application.rb file. Open this file and locate the Rails.application.configure do block. Add the following configuration to set up a basic CORS policy:

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'https://your-allowed-domain.com' # Replace with your production domain
    resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete]
  end
end

In this example, we specify that only requests originating from 'https://your-allowed-domain.com' are permitted. All other origins are denied access. We also specify which HTTP methods are allowed (GET, POST, PUT, PATCH, DELETE) and allow any headers.

Customize your policy

The above example provides a basic CORS policy. Depending on your application's requirements, you can customize it further by adding more origins, methods, or headers to match your specific use case.

Test your CORS policy

Before deploying to production, it's essential to thoroughly test your CORS policy in different environments to ensure it behaves as expected. Verify that your application only responds to requests from allowed origins and denies access to unauthorized domains.

Sanitizing user inputs

Another important practice is to validate the input and sanitize any user-provided data to prevent malicious attacks like SQL injection or cross-site scripting (XSS). Ruby on Rails provides a built-in mechanism for input validation and filtering, so make sure to utilize it properly.

To do this,, use Rails' built-in validation methods for input validation.

# app/models/user.rb

class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i }
  validates :password, presence: true, length: { minimum: 8 }

  # Add any other validations as needed
end

To sanitize user-provided data, you can use the sanitize method or the sanitize_sql method to prevent SQL injection:

# app/controllers/api/v1/users_controller.rb

module Api
  module V1
    class UsersController < BaseController
      def create
        # Sanitize user input
        safe_name = ActiveRecord::Base.sanitize(params[:name])

        # Your code here
      end
    end
  end
end

Additionally, it is recommended to use HTTPS for all API communications to protect sensitive data and prevent eavesdropping. By following these best practices and staying up-to-date with the latest security patches, you can build a secure and reliable API in Ruby on Rails.

Conclusion

By implementing the best practices, such as using environment variables for sensitive data and applying the principle of least privilege, you can significantly reduce the risk of vulnerabilities.

Escape enhances this process by quickly identifying exposed endpoints, and providing actionable remediation—all without the need for traffic monitoring. Plus, with a low time to value of just 15 minutes, you can efficiently secure your Rails applications and stay ahead of potential threats.

Ready to take your Ruby on Rails security to the next level? Request a demo today and see how Escape can help you protect your applications effectively.

Want to learn more? đŸ’¡