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.
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.