Authentication
When we talk about passwords, the commonly used word is "encryption", although the way passwords are used, most of the time, is a technique called "hashing". Hashing and Encryption are pretty similar in terms of the processes executed, but the main difference is that hashing is a one-way encryption, meaning that it's very difficult for someone with access to the raw data to reverse it.
| Hashing | Symmetric Encryption - | |
|---|---|---|
| One-way function | Reversible Operation | |
| Invertible Operation? | No, For modern hashing algorithms it is not easy to reverse the hash value to obtain the original input value | Yes, Symmetric encryption is designed to allow anyone with access to the encryption key to decrypt and obtain the original input value |
Rails Authentication
gem 'bcrypt', '~> 3.1.2'
bundle
rails g model User email password_digest
rake db:migrate
The field password_digest will be used to store the "hashed password", we will see what it looks like in a few seconds but know that the original password will never be stored. The logic for hashing a password the right way would be quite long to implement manually, so instead, we will just add a method provided by bcrypt-ruby to enable all the hashing/storing the hash logic, and we will add a validation for the email:
In app/models/user.rb :
class User < ActiveRecord::Base
has_secure_password
validates :email, presence: true, uniqueness: true
end
Now that we added this method has_secure_password to the user model, we can use two "virtual" attributes on the model, password and password_confirmation.
rails g controller users index new create
In "controllers/users_controller.rb":
class UsersController < ApplicationController
def index
@users = User.all
end
def new
@user = User.new
end
def create
@user = User.new user_params
if @user.save
redirect_to users_path
else
render 'new'
end
end
private
def user_params
params.require(:user).permit( :email, :password, :password_confirmation)
end
end
In "config/routes.rb":
resources :users, only: [:new, :index, :create]
In "views/users/index.html.erb":
<h1> Users index </h1>
<% @users.each do |user|%>
<%= user.email %>
<%= user.password_digest %>
<% end %>
In "views/users/new.html.erb":
<h1>Sign Up</h1>
<%= form_for @user do |f| %>
<% if @user.errors.any? %>
<h2>Form is invalid</h2>
<ul>
<% for message in @user.errors.full_messages %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
<%= f.submit %>
<% end %>
Sessions Controller
Now to allow the user to login and out, we will need to create a sessions controller:
rails g controller sessions new create destroy
Now we can create routes for this controller. In "routes.rb" you should now have:
root "users#index"
resources :users, only: [:new, :index, :create]
get 'login', to: 'sessions#new'
resources :sessions, only: [:new, :create, :destroy]
In "sessions_controller.rb" we'll need to add some logic to handle the user's input for email and password:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
redirect_to root_path
else
render "new"
end
end
def destroy
redirect_to root_url, notice
end
end
Now we need to add a log out form:
In "views/sessions/new.html.erb":
<h1>Login</h1>
<%= form_tag sessions_path do %>
<%= label_tag :email %>
<%= text_field_tag :email %>
<%= label_tag :password %>
<%= password_field_tag :password %>
<%= submit_tag "Log in" %>
<% end %>
In Application controller write:
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def authorize
unless current_user
redirect_to login
end
end
And you're done!