DEV Community

Cover image for Creating a secure API architecture in Rails with few example
Harsh patel
Harsh patel

Posted on

Creating a secure API architecture in Rails with few example

Creating a secure API architecture in Rails with login, logout, signup, list-users, and create-user functionality requires several steps, here is an example of how to implement each step:


  • Use a secure protocol: Use HTTPS for all API requests to ensure that all data is transmitted securely. To force HTTPS in Rails, you can use the following code in your application controller:
force_ssl if: :ssl_configured? def ssl_configured? Rails.env.production? end 
Enter fullscreen mode Exit fullscreen mode
  • Use JSON Web Tokens (JWT) for authentication: Use JWT to authenticate users and protect against cross-site request forgery (CSRF) attacks. You can use the jwt gem to handle JWT in Rails. Here is an example of how to generate and decode JWT tokens in a SessionsController:
class SessionsController < ApplicationController def create user = User.find_by(email: params[:email]) if user&.authenticate(params[:password]) token = JWT.encode({user_id: user.id}, Rails.application.secrets.secret_key_base) render json: {token: token} else render json: {error: 'Invalid login credentials'}, status: :unauthorized end end def decode token = request.headers['Authorization'] decoded_token = JWT.decode(token, Rails.application.secrets.secret_key_base)[0] render json: {user_id: decoded_token['user_id']} end end 
Enter fullscreen mode Exit fullscreen mode

  • Use bcrypt for password hashing: Use bcrypt to hash and salt user passwords to protect against password cracking attacks. To use bcrypt in Rails, you can add the bcrypt gem to your Gemfile and use the has_secure_password method in your User model:
class User < ApplicationRecord has_secure_password end 
Enter fullscreen mode Exit fullscreen mode

  • Use strong parameters: Use strong parameters to prevent mass assignment vulnerabilities and ensure that only the intended attributes are updated. In your controller, you can use the permit method to specify which attributes are allowed:
def create user = User.new(user_params) if user.save render json: user else render json: {error: user.errors.full_messages} end end private def user_params params.require(:user).permit(:name, :email, :password) end 
Enter fullscreen mode Exit fullscreen mode

  • Use CORS: Use the Rails rack-cors gem to configure Cross-Origin Resource Sharing (CORS) and allow cross-domain requests from trusted origins. You can use the config/application.rb file to configure the rack-cors gem:
config.middleware.insert_before 0, Rack::Cors do allow do origins 'https://example.com' resource '*', headers: :any, methods: [:get, :post, :put, :delete] end end 
Enter fullscreen mode Exit fullscreen mode

  • To use rate-limiting and IP blocking to prevent denial-of-service attacks and other malicious activity in a Rails API, you can use the rack-attack gem. Here's an example of how to configure rack-attack in a Rails API:

  • Add the rack-attack gem to your Gemfile and run bundle install:

# Gemfile gem 'rack-attack' 
Enter fullscreen mode Exit fullscreen mode
  • In config/application.rb or config/environments/production.rb, configure rack-attack to use the Rack::Attack::Store::Redis store and set the throttle options:
config.middleware.use Rack::Attack Rack::Attack.cache.store = Rack::Attack::Store::Redis.new # Allow 10 requests per second per IP Rack::Attack.throttle("req/ip", limit: 10, period: 1.second) do |req| req.ip end 
Enter fullscreen mode Exit fullscreen mode
  • Optionally, configure rack-attack to block IPs that exceed a certain number of requests over a certain time period:
# Block IPs that make more than 100 requests per day Rack::Attack.throttle("req/ip", limit: 100, period: 1.day) do |req| req.ip if req.path.include?("/api/") end 
Enter fullscreen mode Exit fullscreen mode
  • Optionally, configure rack-attack to block IPs that exceed a certain number of requests over a certain time period:
# Block IPs that make more than 100 requests per day Rack::Attack.throttle("req/ip", limit: 100, period: 1.day) do |req| req.ip if req.path.include?("/api/") end 
Enter fullscreen mode Exit fullscreen mode
  • Optionally, configure rack-attack to block IPs that exceed a certain number of requests to a specific endpoint :
# Block IPs that make more than 10 requests per minute to a specific endpoint Rack::Attack.throttle("req/ip/endpoint", limit: 10, period: 1.minute) do |req| req.ip if req.path.include?("/api/v1/users/") end 
Enter fullscreen mode Exit fullscreen mode
  • This configuration will limit the number of requests per IP to 10 requests per second, and block IPs that exceed 100 requests per day. You can adjust the limit and period options as needed for your specific use case. It is also important to monitor the rate-limiting and IP blocking rules and adjust them as necessary to ensure that legitimate users are not affected.

  • Use a security scanner: Use a security scanner such as brakeman or bundler-audit to identify and fix any security vulnerabilities in your code.

Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications. It can be installed as a ruby gem and run from the command line. Here is an example of how to install and run Brakeman:

gem install brakeman brakeman 
Enter fullscreen mode Exit fullscreen mode

This will run Brakeman on your Rails application and generate a report of any potential security vulnerabilities it finds.

Another option is to use bundler-audit which is a gem dependency security scanner. It can be installed as a ruby gem and run from the command line. Here is an example of how to install and run bundler-audit:

gem install bundler-audit bundle-audit check 
Enter fullscreen mode Exit fullscreen mode

This will run bundle-audit on your Rails application and generate a report of any known vulnerabilities in your application's dependencies.

Both Brakeman and bundler-audit are very useful tools for identifying and fixing security vulnerabilities in your Rails API. It is important to schedule regular scans and address any vulnerabilities that may be found.

  1. To use authorization in a Rails API, you can use a gem such as Pundit or CanCanCan. Both gems provide a simple and flexible way to handle authorization and ensure that users can only perform actions that they have been granted permission to do.

Here's an example of how to use Pundit to handle authorization in a Rails API:

  • Add the **pundit **gem to your Gemfile and run bundle install:
# Gemfile gem 'pundit' 
Enter fullscreen mode Exit fullscreen mode
  • Generate a Policy class for each of your models:
rails g pundit:policy User 
Enter fullscreen mode Exit fullscreen mode
  • In the Policy class, define which actions a user is allowed to perform based on their role or permissions:
# app/policies/user_policy.rb class UserPolicy < ApplicationPolicy def index? user.admin? end def show? user.admin? || user.id == record.id end def create? user.admin? end def update? user.admin? || user.id == record.id end def destroy? user.admin? end end 
Enter fullscreen mode Exit fullscreen mode
  • In your controllers, use the **authorize **method to check if a user is authorized to perform a specific action:
# app/controllers/users_controller.rb class UsersController < ApplicationController before_action :set_user, only: [:show, :update, :destroy] def index @users = User.all authorize User render json: @users end def show authorize @user render json: @user end def create @user = User.new(user_params) authorize @user if @user.save render json: @user, status: :created, location: @user else render json: @user.errors, status: :unprocessable_entity end end def update authorize @user if @user.update(user_params) render json: @user else render json: @user.errors, status: :unprocessable_entity end end def destroy authorize @user @user.destroy end private def set_user @user = User.find(params[:id]) end def user_params params.require(:user).permit(:name, :email, :password) end end 
Enter fullscreen mode Exit fullscreen mode
  • Optionally, you can handle the unauthorized exception by adding the following code in application_controller.rb
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized render json: { error: "You are not authorized to perform this action." }, status: :unauthorized end 
Enter fullscreen mode Exit fullscreen mode

CanCanCan is another gem for authorization in Rails. It allows you to define ability rules to determine what actions a user is allowed to perform. Here is an example of how to use CanCanCan to authorize a user to update a Post:

class Ability include CanCan::Ability def initialize(user) if user.admin? can :manage, :all else can :update, Post, author_id: user.id end end end class PostsController < ApplicationController load_and_authorize_resource def update if @post.update(post_params) render json: @post else render json: {error: @post.errors.full_messages} end end private def post_params params.require(:post).permit(:title, :body) end end 
Enter fullscreen mode Exit fullscreen mode

Both Pundit and CanCanCan are great options for handling authorization in a Rails API. They both provide a flexible and powerful way to define and enforce access controls, but with different syntax. It's important to choose the one that best fits your needs and you are comfortable with.


  • Conducting regular security audits is an important step in ensuring that your Rails API is secure and up to date. Here are a few ways to perform regular security audits in a Rails API

  • Use a security scanner such as Brakeman or Bundler-audit to automatically scan your code for known vulnerabilities. These scanners can be integrated into your development process and run on a regular basis.

# Run Brakeman brakeman # Run bundler-audit bundle-audit 
Enter fullscreen mode Exit fullscreen mode
  • Perform manual code reviews to check for any potential security issues that may not be detected by automated scanners. This can include reviewing code for SQL injection vulnerabilities, cross-site scripting (XSS) vulnerabilities, and other potential issues.

  • Review your infrastructure and configurations to ensure that they are secure and up to date. This can include checking that all servers, databases, and other components are running the latest versions and that they are configured securely.

  • Regularly monitor and log all API activity for auditing and security purposes. This can include monitoring for suspicious activity, such as multiple failed login attempts, and logging all requests to your API for later review.

  • Conduct penetration testing to simulate real-world attacks and identify vulnerabilities in your API. This can include using tools such as Metasploit to test for vulnerabilities and simulating attacks to identify any potential security issues.

Here's an example of how you might integrate Brakeman into your development process:

Add the brakeman gem to your Gemfile and run bundle install.

# Gemfile gem 'brakeman' 
Enter fullscreen mode Exit fullscreen mode

Run brakeman on your application to generate a report on any potential security issues:
brakeman

Review the report generated by Brakeman and address any issues found.
Integrate Brakeman into your continuous integration process to run the scan automatically on each build.

It is also important to keep in mind that security is an ongoing process and you should always be on the lookout for new vulnerabilities and updates to the libraries you use.


  • Keep your app updated: Keep your Rails app and its dependencies updated to ensure that any security vulnerabilities are patched promptly

It's important to keep your Rails app and its dependencies up-to-date to ensure that any security vulnerabilities are patched promptly. You can use the bundle-outdated command to check which gems in your app are outdated. Here is an example of how to use it:
bundle outdated
This will show a list of gems that have newer versions available, and you can update them by running:
bundle update <gem_name>

It's important to keep an eye on the security releases of the gems you are using in your app, you can use a service like RubySec to get notifications of new vulnerabilities in the gems you are using.

Another important step to take is to keep your Rails version up-to-date. You can check the version of Rails you are currently using by running:
rails -v
You can update Rails by modifying the version in your Gemfile and running:
bundle update rails
and then running the necessary commands to update your application to the new version of Rails.

It's important to schedule regular updates for your app and its dependencies, and to test your app thoroughly after each update. It's also recommended to keep a backup of your app before doing any updates, in case something goes wrong.

Thanks,
Harsh Umaretiya

Top comments (0)