Pretty much any application that you want to make will include some portions that need to be restricted.
Maybe you have certain pages you want to block, or maybe you just want to make sure users can log in to your site and post items.
Whatever it is, making sure that your website or application has the ability to log in and log out is vital.
The good news is, authentication in Ruby on Rails 6 is incredibly easy to implement. You’ll have this up and running in no time.
Getting started with authentication with Rails 6
The first thing you need to do is make sure you have a users model. Go ahead and add the users model to your app by generating a migration, adding a users table, and then running your migrations. When you set up your user model in the migration, make sure it has a column called “password_digest” and it is the type string.
class AddPasswordDigestToUsers < ActiveRecord::Migration[6.1] def change add_column :users, :password_digest, :string end end
In the above image, I already have the users table created, so I just needed to add the password_digest column.
Using the bcrypt gem for authentication
Your gem file should have a gem called “bcrypt” that is commented out. Go ahead and uncomment that gem, or add it to your gem file like so:
gem 'bcrypt', '~> 3.1.7'
We’re not going to get in the specific of the bcrypt gem and how it works because this isn’t an article about cryptography. Just know that this gem is what’s going to power our authentication system.
Go to your user model (user.rb) and add the method has_secure_password. You get this from the bcrypt gem. If your user model doesn’t have any validations or other code, it should look something like this:
class User < ApplicationRecord has_secure_password end
Next, we need to set up our routes so we can create users and sessions in our controllers.
We’re going to add a few routes here. I’ll list the code and then I’ll explain it.
get "signup", to: "users#new" get "login", to: "sessions#new" post "login", to: "sessions#create" delete "logout", to: "sessions#destroy" resources :users, except: [:new]
With authentication, we’re working with both a sessions_controller and a users_controller. The sessions paths above are very similar to other CRUD actions you’ve probably created in your application – you have new, create, and destroy.
At the bottom, we have the resource :users to generate the routes. We exclude :new so our signup page is /signup and not /users/new.
The users form and users_controller
These will be similar to other models in your app. My form to create a user looks like this:
<%= form_with(model: @user, local: true) do |f| %> <div class="form-group"> <%= f.label :username %><br/> <%= f.text_field :username, class: "form-control" %> </div> <div class="form-group"> <%= f.label :email %><br/> <%= f.email_field :email, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password %><br/> <%= f.password_field :password, class: "form-control" %> </div> <div class="form-group"> <%= f.submit %> </div> <% end %>
Nothing should look too out of the ordinary here, except the fact we are using the field :password instead of :password_digest. The has_secure_password method we used earlier gives us the user model the virtual method user.password.
By the way, this form is under views/users/new.html.erb.
In the users_controller, we’re just going to do the same as we would with any other model. Make sure we have our new and create methods.
Initializing the @user in the new method is going to allow us to work with errors when we render our form if the user’s submission isn’t accepted. Here’s the relevant code for new and create.
def new @user = User.new end def create @user = User.new(user_params) if @user.save flash[:notice] = "User created." redirect_to root_path else render 'new' end end private def user_params params.require(:user).permit(:username, :email, :password) end
Great! We can now create a user, the first step to authentication in rails. Now, let’s get to the good stuff.
The log in form, sessions, and the sessions_controller
Let’s create a form to log in. This is going to go in views/sessions/new.html.erb.
<%= form_with(scope: :session, url: login_path) do |f| %> <div class="form-group"> <%= f.label :email %><br/> <%= f.email_field :email, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password %><br/> <%= f.password_field :password, class: "form-control" %> </div> <div class="form-group"> <%= f.submit "Log in", class: "btn btn-primary" %> </div> <% end %>
This is very similar to your new users form. Here, we’re getting the email address so we can find the user and then getting the password to check against the password they used to login.
Notice we’re using scope: :session, url: login_path here.
Create a sessions_controller and put the following code. I’ll explain what it does.
def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) session[:user_id] = user.id flash[:notice] = "Logged in successfully." redirect_to user else flash.now[:alert] = "There was something wrong with your login details." render 'new' end end def destroy session[:user_id] = nil flash[:notice] = "You have been logged out." redirect_to root_path end
First, we are finding the user by the email they entered in the login in form. You can chose to use something else like username if you want – I just went with email.
Next, we’re checking if that user exists and if the password is correct. The user.authenticate method takes in the password that was submitted on the form. The method then checks this against the hashed password in the database. If the password is correct, the user is returned.
After that, we’re setting a session variable by using session[:user_id] = user.id. Later, on the the app, we’ll check if the session[:user_id] is not equal to nil. If it’s not, that means the user is logged in.
The destroy method logs the user out. As you can see, we’re just toggling session[user_id] to log in and log out.
Now, in order to protect a route, all you have to do is something like this.
if session[:id] #means the user is logged in # allow the user to do cool stuff else redirect_to login_path end
Because we need to know if the user is logged in a lot we’re going to set up some special helper methods in our application_controller.rb file.
helper_method :current_user, :logged_in? def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end def logged_in? !!current_user end def require_user if !logged_in? flash[:alert] = "You must be logged in to perform that action." redirect_to login_path end end
These methods will be used in our controllers and views to check for things like:
if logged_in? link to logout path else link to login path end
<h1>Welcome <%=current_user.username %> </h1>
If you want to learn more about web development, make sure to follow me on Twitter.