Ruby on Rails has_secure_password Tutorial
In Ruby on Rails Feb 23, 2022
Updated on May 13, 2023
The has_secure_password method adds methods to encrypt a password using the bcrypt algorithm. Also, it adds methods to authenticate against such a bcrypt password. For this method to work, the user model must have a password_digest attribute. In this tutorial, we'll build a simple application with a user model, and we will discuss in detail how we can use the has_secure_password method in our application. We will also use sessions to allow users to log in and log out. We will use Rails 7.0.2.2 and Ruby 3.1.1.
Set Up the Application
Let's start by creating a new application. In the terminal, we type:
rails new my_application
Then we change the directory to its folder:
cd my_application
The bcrypt gem is not installed by default, so we need to uncomment this line in the Gemfile
:
gem "bcrypt", "~> 3.1.7"
Then we run:
bin/bundle install
We need a home page for the application. Therefore we'll generate a controller with an index
action:
bin/rails g controller Pages index
In the config/routes.rb
file, we delete the auto-generated route for the index
action and
instead set the root
route to it:
root 'pages#index'
Generate a User Scaffold
From the terminal, we type:
bin/rails g scaffold User
The user
model has two attributes: email
and password_digest
.
The password_digest
field in the database is required by the has_secure_password
method. The
encrypted form of the password is saved in this field. The password that the user sets is never saved to the database.
Let's generate a migration to add these fields:
bin/rails g migration AddColumnsToUsers email:string password_digest:string
Then we run the migration:
bin/rails db:migrate
Now we add the has_secure_password
method in the app/models/user.rb
file:
class User < ApplicationRecord
has_secure_password
end
Now, we must edit the new
user
form to add three input
fields corresponding to
the user
model: email
, password
, and password_confirmation
:
<%= form_with(model: user) do |form| %>
<% if user.errors.any? %>
<div style="color: red">
<h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% user.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<p>
<%= form.label :email %>
<%= form.email_field :email %>
</p>
<p>
<%= form.label :password %>
<%= form.password_field :password %>
</p>
<p>
<%= form.label :password_confirmation %>
<%= form.password_field :password_confirmation %>
</p>
<p>
<%= form.submit %>
</p>
</div>
<% end %>
The password
and the password_confirmation
parameters are not in the strong parameters list
in the users
controller. So let's edit the user_params
method to add them:
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
Now, we can go to http://localhost:3000/users/new and add users.
FIND THE current user
Next, we'll talk about user authentication. First, we'll define a @_current_user
method to find the user
with the id
stored in the session. In the application_controller.rb
file in the
app/controllers/
directory, we add the following private method:
private
def current_user
@_current_user ||= session[:current_user_id] &&
User.find_by(id: session[:current_user_id])
end
And we set it as a before filter in the before_action
method of the same controller. At the top, we put
this line:
before_action :current_user
Create a Session Controller
Next, we want to add a mechanism to add and remove the user from the session. Let's create the
sessions_controller.rb
file in the app/controllers/
directory. In the terminal type:
bin/rails g controller Sessions
Then we add a new
and a create
method to it:
def new
end
def create
user = User.find_by(email: params[:email])
if user != nil && user.authenticate(params[:password])
session[:current_user_id] = user.id
redirect_to root_path
flash[:notice] = 'Welcome back.'
else
redirect_to log_in_path
flash[:notice] = 'E-mail and/or password is incorrect.'
end
end
The authenticate
method returns the user if the password is correct and false otherwise.
Let's define the routes to these actions in the config/routes.rb
file:
get 'log-in', to: 'sessions#new'
post 'log-in', to: 'sessions#create'
In the app/views/sessions/
folder we create a new
view where we put the login form:
<p style="color: green"><%= notice %></p>
<h1> Log In </h1>
<%= form_with url: log_in_path do |form| %>
<p><%= form.label :email %></p>
<p><%= form.email_field :email, required: true %></p>
<p><%= form.label :password %></p>
<p><%= form.password_field :password, required: true %></p>
<p><%= form.submit 'Log In' %></p>
<% end %>
Finally, we want the user to be able to log out. We create a destroy
action in the sessions
controller:
def destroy
session.delete(:current_user_id)
@_current_user = nil
redirect_to root_path
flash[:notice] = 'Goodbye.'
end
Then we define a route to it:
delete 'log-out', to: 'sessions#destroy'
Add LogIn, LogOut, and SignUp buttons to the Home Page
On our home page, we put a login button and a signup button if there is no user in the session, and a logout button if the user is logged in.
<p style="color: green"><%= notice %></p>
<% if @_current_user %>
<%= button_to 'Log Out', log_out_path(@_current_user), method: :delete %>
<% else %>
<%= link_to 'Log In', log_in_path %>
<%= link_to 'Sign Up', new_user_path %>
<% end %>
Conclusion
We set up relatively easy a simple user authentication using the Rails has_secure_password
method and
sessions.