Creating a secure API architecture in Rails with few example
Tags: securewebapp, rails, railsapi, security
Published: 2023-01-25 17:11:28

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

```

-  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

```

---

- 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

```

---

- 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

```

---

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

---

- 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'
```

- 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

```

-  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

```

- 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

```

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

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

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.

8. 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'
```

- Generate a Policy class for each of your models:

```
rails g pundit:policy User
```

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

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

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

- 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'
```
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