3. Generate Model for user Authentication

devise is one of popular authentication gem for rails. But, we are not using any gem here for user authentication here so that we can understand it from the base. We will be familiarized with working mechanism. Once we get basic knowledge, we can use them since they are for us.

So, lets start creating model and controllers for it.

lets generate controller for users

rails g controller home index

Output:

create  app/controllers/home_controller.rb
route  get 'home/index'
invoke  erb
create    app/views/home
create    app/views/home/index.html.erb
invoke  test_unit
create    test/controllers/home_controller_test.rb
invoke  helper
create    app/helpers/home_helper.rb
invoke    test_unit

The rails generate command above seem new for us. The syntax is rails g controller controller_name method_name(s). As seen in the output, the command generated view file for index method inside app/views/home/index.html.erb.

Password Digest

We only store the password digest in the database. For that we are using bcrypt gem

Lets add the gem to the gemfile and run bundle

gem 'bcrypt', '~> 3.1', '>= 3.1.20'
bundle install

User Model

We will be creating a scaffold of User.

rails g scaffold user email:uniq password:digest

Scaffolding

A scaffold is a set of automatically generated files which forms the basic structure of a Rails project. These files include:

  • A controller
  • A model
  • Views for every standard controller action (index, edit, show, new)
  • A new route
  • And a migration to prepare your database.

We can add additional fields to the migration file according to our requirements.

The email:uniq stores the email address and also creates the unique database index. Incase of password_digest, the generator creates a password_digest field which asks for an additional password_confimation in the form.

The has_secure_password is used to encrypt and authenticate the passwords using the bcrypt gem. It encrypts the password and provides the authenticate method to authenticate with the password.

Lets add some validations to user model.

class User < ApplicationRecord
  has_secure_password
  validates :email, presence: true, uniqueness: true
end

This makes sure that we have a unique email address.

We can start the rails server and create the new user and don't forget to add the routes for users.

Rails.application.routes.draw do
  resources :users
end

Sessions

A session is created when a user logs in and is destroyed when same user logs out.

Session

"Session" is the term used to refer to a user's time browsing a web site. It's meant to represent the time between their first arrival at a page in the site until the time they stop using the site. In practice, it's impossible to know when the user is done with the site.

Lets create a sessions controller with actions:

rails g controller sessions new create destroy

and in sessions_controller we have

class SessionsController < ApplicationController
  def new
  end

  def create
  end

  def destroy
  end
end

Let's find a user using their email and if the user is found from it's email and the password they have given is correct, create a new session for the user and redirect the user to root path.

# code above here remain unchanged
def create
  user = User.find_by_email(params[:email])

  if user && user.authenticate(params[:password])
    session[:user_id] = user.id
    redirect_to root_url, notice: "Logged in!"
  else
    flash.now[:alert] = "Email or password is invalid"
    render :new, status: :unprocessable_entity
  end
end
# code below here remain unchanged

To destroy the user session, we simply set the session for user to nil and redirect to root path.

def destroy
  session[:user_id] = nil
  redirect_to root_url, notice: "Logged out!"
end

and the new form for sessions should look like this:

<% if alert %>
  <div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
    <span class="font-medium"><%= alert %></span>
  </div>
<% end %>

<h4>Login</h4>
<%= form_tag sessions_path do |form| %>
  <div>
    <%= label_tag :email %>
    <%= text_field_tag :email %>
  </div>
  <div>
    <%= label_tag :password %>
    <%= password_field_tag :password %>
  </div>
  <div>
    <%= submit_tag "Login" %>
  </div>
<% end %>

now lets add the routes for sessions as well

Rails.application.routes.draw do
  root 'home#index'

  resources :users
  resources :sessions, only: [:new, :create, :destroy]
  get 'signup', to: 'users#new', as: 'signup'
  get 'login', to: 'sessions#new', as: 'login'
  delete 'logout', to: 'sessions#destroy', as: 'logout'
end

The user can use http://localhost:3000/login to login and http://localhost:3000/logout to logout easily.

current_user

current_user is a helper method that is used to get the logged in user in most rails applications.

We can use this method by adding it in the application_controller.

#app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  helper_method :current_user

  def current_user
    if session[:user_id]
      @current_user ||= User.find(session[:user_id])
    else
      @current_user = nil
    end
  end
end

We will be using this method in our app.

Lesson list