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
.
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
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. We need to migrate the database after creating new migration file by:
rails db:migrate
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
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 %>
If you want a login page using flowbite design then add following code in sessions/new.html.erb
<% if alert %>
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:text-red-400" role="alert">
<span class="font-medium"><%= alert %></span>
</div>
<% end %>
<section class="bg-gray-50">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<div class="flex items-center mb-6 text-2xl font-semibold text-gray-900"> Login page </div>
<div class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Sign in to your account
</h1>
<form class="space-y-4 md:space-y-6" action="<%= sessions_path %>" method="post">
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@example.com" required="">
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
<input type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
</div>
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Sign in</button>
<p class="text-sm font-light text-gray-500 dark:text-gray-400">
Don’t have an account yet? <a href="#" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
</p>
</form>
</div>
</div>
</div>
</section>
You may change the design as your preference. now lets add the routes for sessions as well N
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 access login page.
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.
Open rails console by rails c
in terminal and create a user
User.create(email: "test@example.com", password: "password123", password_confirmation: "password123")
After this you can login with the credential given above in login page.