When building an app, you’ll probably need to handle user authentication in one form or another. In Rails applications, several pre-built libraries and gems make it a breeze to build user authentication including well-known libraries like Devise, Doorkeeper, and OmniAuth. These libraries work well out of the box, but there’s a caveat, as developers must follow certain configuration and design rules for each library. Therefore, some developers might prefer to roll out their own authentication layer and configure it to their specifications. In this article, we’ll explain how to build a custom authentication layer from scratch for a Ruby on Rails app.
Prerequisites
You’ll need the following to complete this tutorial:
– Some knowledge of working with Ruby on Rails.
– A local installation of Ruby on Rails; we’ll be using Ruby version 3.2.2 and Rails version 7.0.7 for this tutorial.
Quick note: The source code for this tutorial can be found here.
Before diving into the tutorial, we’ll explain why user authentication is needed.
The importance of user authentication
Generally speaking, authentication is the process of verifying the identity of an app’s users before giving them access to certain resources. It is a very fundamental feature of any app and helps with the following:
1. Preventing unauthorized access – Authentication, in whatever form it’s been implemented, ensures that only authorized persons access an app’s resources. Without it, your app would be open for anyone to access, which could lead to data loss and abuse.
2. Accountability – With authentication, it’s possible to associate different actions performed in an app with the authenticated user who performed them. This forms a very powerful accountability mechanism that can help maintain app security and provides an audit history of actions performed within an app.
3. Providing a personalized user experience – In many multi-tenant software-as-a-service (SaaS) apps, providing a personalized user experience is made possible by building a user authentication layer. Thus, regardless of whether the app has ten or ten-thousand users, each user has a tailored experience.
Earlier in this article, we pointed out some of the reasons a developer might want to roll out their own authentication solution. Next, we’ll highlight some of these ready-made solutions and note where they fall short.
A brief look at current authentication solutions
Several pre-built user authentication solutions are available to Rails developers, including the following:
1. Devise – A very popular authentication library with a diverse set of features ranging from user sign up to password resets, all of which can help developers quickly build a robust authentication system for a Rails application. Although it’s possible to build a production-ready user authentication system using Devise, it’s worth noting that the library has some limitations: pre-configured views, controllers, and modules that may take time to customize, and some opinionated defaults that might not work for every project.
2. Clearance – A lightweight authentication library that tries to offer simplicity and customization for building user authentication in Rails apps. Clearance might work well for projects that don’t require complex user authentication rules, but if you need to perform extensive customizations, you will likely find the gem to be limiting.
3. Sorcery – Yet another excellent gem that allows developers to choose the particular authentication modules they would like to have in their app. Some of the concerns that come with using this gem include less frequent updates compared to other libraries and a lack of extensive documentation when compared to a library like Devise.
Other solutions are Doorkeeper, OmniAuth, JWT, and Authlogic, but we won’t get into them for now. We should note that regardless of the library used, you will likely end up with a lot of customization overhead if you need very specific authentication features and have to deal with opinionated defaults that come packaged with most of these pre-built authentication solutions.
With these limitations in mind, we’ll begin building a user authentication system from scratch in the next section.
Setting up the project
Run the command below to generate a new app:
<code><pre>rails new simple_auth_app</pre></code>
We should now have a simple Rails 7 app that will form the foundation for building the user authentication features from scratch.
Creating a user model
After creating our bare-bones Rails app, the next step is to create a User model. You might ask why we need this model. As obvious as it might sound, one of the basic features of a user authentication system is the ability to store and retrieve user credentials. These credentials include the user’s name, email address, password, and other personal information, and we’ll need a model to store and retrieve these attributes.
In the new app’s root, run the command below to generate the User model with some basic attributes:
<code><pre>rails generate model User email password_digest</pre></code>
This command should generate a model, some tests, and a migration to create the users table.
Before we continue, you may have noticed that we seem to be missing the user’s password, but we have password_digest. The reason for this discrepancy is whenever you’re dealing with user authentication, security is of utmost importance. Since it would be bad practice to store a user’s password in plain text, we instead take the password supplied by the user when they sign up for the first time, encrypt it, and then save the encrypted form in the password_digest field. This field would be useless to anyone who accesses it via nefarious means.
However, don’t worry about this for now; we’ll cover it in detail later in this tutorial. For now, open up the generated migration and edit it as shown below:
<pre><code># db/migrate/XXXXX_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :email
t.string :password_digest
t.timestamps
end
add_index :users, :email, unique: true # Add this line
end
end</code></pre>
Note: We add an index on the email column with a unique constraint to ensure that all user-supplied emails are unique.
Next, run the migration to create the users table:
With that done, let’s now turn our attention to something we highlighted earlier, encrypting a user’s password.
Encrypting user passwords
As mentioned, storing user passwords in plain format defeats the purpose of user authentication since anyone who manages to access the database can easily access other users’ accounts. We also mentioned that the way to mitigate this risk is by encrypting user passwords.
For Ruby on Rails apps, we can do this easily using the bcrypt gem, the Ruby variant of the OpenBSD bcrypt() password-hashing algorithm. By default, new Rails apps come with the gem commented out in the Gemfile, and all we need to do is to uncomment it:
<pre><code># Gemfile
…
gem “bcrypt”, “~> 3.1.7”
…
</code></pre>
Run bundle install to install it.
Next, to reference bcrypt within the User model, we use a nifty Active Model class method called has_secure_password that accepts two arguments:
A password attribute supplied by the user.
A set of validations the password has to adhere to, such as password length. However, if you don’t want to have any validations, just pass validations: false as the second argument.
Now that we have an idea of what has_secure_password is all about, let’s add it to the User model, as well as some basic validations, to ensure the user submits a valid email. For this last part, we’ll use the standard Ruby library’s URI::MailTo and, specifically, the EMAIL_REGEXP module:
<pre><code># app/models/user.rb
class User < ApplicationRecord
has_secure_password
validates :email, presence: true
validates_format_of :email, with: URI::MailTo::EMAIL_REGEXP
end</code></pre>
This should be enough to get us going. We’ll progressively add more features to the User model, but for now, let’s delve into views.
Adding controllers and views
Allowing users to register on our app requires us to present them with the views to do so. We’ll keep it as basic as possible with a simple user registration form and another one for logging in. We’ll also add a basic dashboard view to redirect a user when they successfully log in and a simple home page where they’ll be redirected when they log out.
The homepage controller
Let’s start with the homepage. Run the command below to generate it:
<pre><code>rails generate controller home welcome</code></pre>
This command generates a bunch of files and inserts a relevant route to routes.rb. Let’s modify this route so that it becomes the root route:
<pre><code># config/routes.rb
root ‘home#welcome'</code></pre>
For this page, we’ll keep things simple and just add a title “Homepage”, a link to sign up,…
Source link