How to set up user authentication and authorization in Ruby on Rails

How to set up user authentication and authorization in Ruby on Rails

In this blog post, we will explore the basic steps for configuring user authentication and authorization in a Ruby on Rails application. As web applications grow in complexity and importance, ensuring that only authorized users can access certain parts of the application is critical. This post will walk you through implementing a robust and secure authentication system using popular Rails tools such as Devise and Pundit. Whether starting a new app from scratch or adding authentication to an existing project, this guide will provide you with the necessary knowledge and practical steps to implement effective user authentication and authorization.

User authentication vs. authorization

To begin, let's clarify the difference between authentication and authorization, as these concepts are frequently conflated.

authorization vs authentication

Image Source: Medium

According to OWASP, ‘Authentication is the process of verifying that an individual, entity, or website is who or what it claims to be by determining the validity of one or more authenticators (like passwords, fingerprints, or security tokens) that are used to back up this claim.’

In simpler terms, authentication answers the question, ‘Who are you?’. It confirms a user's or system's identity through credentials such as usernames and passwords, biometric data, or security tokens. This step ensures that the person or system accessing the application is legitimate.

Regarding Authorization, OWASP uses NIST’s definition, which states that Authorization is ‘the process of verifying that a requested action or service is approved for a specific entity.’

Simply put, Authorization answers the question, ‘What can you do?’. It determines whether an authenticated user has the necessary permissions to access specific resources or perform certain actions within the application. This step is crucial in enforcing access control and ensuring that users can only interact with data and features they are explicitly allowed to use.

Significance of authentication and authorization in Ruby on Rails

Understanding the difference between authentication and authorization is crucial for building secure applications. Authentication ensures that users are who they claim to be, while authorization ensures that authenticated users have the correct permissions to access resources and perform actions. Both steps are necessary to protect sensitive data and functionality within your application.

Authentication and authorization gems in Ruby on Rails

Let’s look into two popular gems: Devise for authentication and Pundit for authorization. These tools provide robust and scalable solutions, ensuring your application remains secure as it grows in complexity and user base.

Ruby on Rails authentication with Devise

Devise is a flexible and comprehensive authentication solution for Ruby on Rails applications. It is regarded as the most popular and battle-tested gem in the Rails ecosystem for handling user authentication. Devise provides a wealth of features out of the box, including database authentication, password recovery, session management, and more, making it an excellent choice for developers who need a robust authentication system without having to build it from scratch.

Key features of Devise:

  • Database Authentication: Manages user sessions and ensures users are who they claim to be.

  • Password Recovery: Provides a secure way for users to reset their passwords.

  • Session Management: Handles user sessions, including sign-in and sign-out functionality.

  • Email Confirmation: Ensures that users have verified their email addresses.

  • Account Locking: Protects against brute force attacks by locking accounts after several failed login attempts.

  • Timeoutable: Automatically logs out users after a specified period of inactivity.

NOTE: Devise uses bcrypt for password hashing. Why do I mention it? Because bcrypt has limitations, I believe that if you use Devise, you should also know the constraints of the tools it uses under the hood. For more details on these limitations and how to mitigate them, go to this article:  - More Secure Passwords in Bcrypt — Beating the 72 Bytes Limitation.

Configuring Devise in Rails App

Now, we will walk you through the process of configuring Devise in a Ruby on Rails application. After following the steps below, a secure and fully functional authentication system will be integrated into your Rails app, ready to handle user accounts easily.

First, add the following line to your Gemfile:

# Gemfile

gem "devise"

You can check out the latest version on the RubGems website.Then, run bundle install to install it.

Next, run the generator using the rails generate devise:install command.

At this stage, several instructions will be displayed in the console. Read them and adjust them accordingly for your application.

As our application is NOT API-only, this is how we adjusted our settings:

# config/environments/development.rb

config.action_mailer.default_url_options = { host: "localhost", port: 3000 }


To proceed, you also need an example controller. Use the rails generate controller examples index show command to create the example controller (app/controllers/examples_controller.rb).

With the example controller already created, you can customize the routes:

# config/routes.rb

root "examples#index"

Additionally, if needed, you can also customize the layout:

# app/views/layouts/application.html.erb

<body>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>
  <%= yield %>
</body>

Generating user model with Devise

At this point, you are ready to add Devise to any model using the rails generate devise User command (it’s usually User but could also be Admin, Member, etc.).

Side note: Devise is based on the principle of modularity, allowing you to use only the necessary components. By default, Devise includes the following modules:

  • Database Authenticatable: hashes and stores a password in the database to validate a user's authenticity while signing in. The authentication can be done through POST requests or HTTP Basic Authentication.

  • Registerable: handles signing up users through a registration process, allowing them to edit and destroy their accounts.

  • Recoverable: resets the user password and sends reset instructions.

  • Rememberable: generates and clears a token for remembering the user from a saved cookie.

  • Validatable: provides validations of email and password. It's optional and can be customized so you can define your validations.

You can find more information on all available models here RubyDoc.

Next, run rails db:migrate.

Now, to be able to test your authentication mechanism, you need to add the ability to pass to the login form in the views that will be displayed to users:

# app/views/examples/index.html.erb

<h1>Examples#index</h1>
<%= link_to 'Sign in', new_user_session_path %>

Your authentication mechanism should look like this in the screenshots below:

Authentication mechanism

Customizing Devise views

If you would like to edit the appearance of the registration form, login form, or any of the forms provided by the devise gem, you can generate these views using the rails generate devise:views command. This command will create them in the app/views/devise folder and allow their further editing.

Authorization with Pundit

Pundit is a powerful and flexible authorization library for Ruby on Rails applications. It provides a set of helpers that guide you in leveraging regular Ruby classes to build a straightforward, robust, and scalable authorization system.

Key features of Pundit:

  • Plain Ruby Classes: Pundit uses plain old Ruby classes (PORCs) to define authorization policies. Each policy corresponds to a model and contains methods determining whether a user can perform a specific action.

  • Granular Permissions: Pundit allows you to define granular permissions at the method level within your policies. This fine-grained control ensures that only authorized users can access specific resources or perform certain actions.

  • Ease of Use: With Pundit, you can easily integrate authorization checks into your controllers using concise and readable syntax. It makes your codebase more maintainable and easier to understand.

  • Scalability: Pundit is designed to scale with your application. You can add new policies and authorization logic without significant refactoring as your app grows.

These two gems are more than enough to build entire authentication and authorization mechanisms in our Ruby on Rails application. As we’ve discussed their functionalities and advantages, let’s move on to the more practical part: configuration and implementation. 

Adding authorization with Pundit

In this section, we'll add authorization to our Rails application using Pundit. Pundit provides a simple and effective way to manage user permissions. To get started, add Pundit to your Gemfile and install it. 

Add the following line to your Gemfile:

# Gemfile

gem "pundit"

You can check out the latest version on the RubGems website.Then, run bundle install to install it.Include Pundit::Authorization in your application controller:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include Pundit::Authorization
end

Next, you need to run the generator using rails g pundit:install command.

Creating policies for Authorization

Now, you can start creating your own policies; in our case, we would like to create a policy for the examples_controller created for the authentication mechanism:

# app/policies/example_policy.rb

class ExamplePolicy
  attr_reader :user, :example_object

  def initialize(user, example_object)
    @user = user
    @example_object = example_object
  end

  def index?
    true
  end

  def show?
    user&.created_at&.today? && example_object[:show] == true
  end
end

Let's now move on to the different sections of our policy file:

def initialize(user, example_object)
  @user = user
  @example_object = example_object
end
  • The first argument is the user. In your controller, Pundit will call the current_user (the current_user method comes from the devise gem) method to retrieve what to send into this argument.

  • The second argument is a scope on which to perform a query. It will usually be an ActiveRecord class or an ActiveRecord::Relation, but it could be something else entirely.

Here, you allow any (even not logged-in) user to access this page:

def index?
  true
end

Here, only logged-in users whose accounts were created today and when the value of the show key in the example_object hash is true will get access to this action.

  def show?
    user&.created_at&.today? && example_object[:show] == true
  end

NOTE: In our case, example_object is neither ActiveRecord nor ActiveRecord::Relation instance, but a simple hash.

Now, let's customize the implementation of the controller:

class ExamplesController < ApplicationController
  def index
    authorize Hash, policy_class: ExamplePolicy
  end

  def show
    example_object = { show: true }

    authorize example_object, policy_class: ExamplePolicy
  end
end

Great! Now, it is time to discuss the implementation of the index and show actions: To simplify this example, the value of example_object is hardcoded as Hash instance:

def index
  authorize Hash, policy_class: ExamplePolicy
end

def show
example_object = { show: true }

authorize example_object, policy_class: ExamplePolicy
end

However, in most cases, it will be an ActiveRecord or ActiveRecord::Relation instance. We use the policy_class: ExamplePolicy because, without it, the Pundit would try to hit the HashPolicy file since our example_object is an instance of the Hash class, which is the default behavior of the Pundit gem.

Best practices for security in Rails: implementing authentication and authorization

Ensuring strong user authentication and authorization in a Ruby on Rails application is crucial for maintaining security and controlling access as your application becomes more complex. By using Devise and Pundit, you can easily establish a secure and scalable system to manage user identities and permissions. These tools help guarantee that only authenticated users can access your application and that they have the necessary permissions to interact with specific resources. Following the steps outlined in this guide will prepare you to implement effective user authentication and authorization in your Rails projects.

Review the devise and pundit documentation to discover the vast number of other options and settings you can use and customize to tailor the authentication and authorization mechanisms to your application's needs.

Patryk Gramatowski