Watch the video of this screencast on YouTube (14:33 min).
This screencast shows how to add a simple password authentication for your Ruby on Rails 4.2 application without using the big shots like Devise or OmniAuth (see ruby-toolbox.com for a list of popular authentication gems).
We start with a fresh Ruby on Rails application.
rails new shop
cd shop
For the has_secure_password
method we need the bcrypt
gem. And because we don’t need a JSON API in this application deactivate the jbuilder
gem.
Gemfile
[...]
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.0'
[...]
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
[...]
After saving the Gemfile
we need to run bundle
.
bundle
As a start we create a Page
controller with an index
view:
rails g controller Page index
We use that view as the root_path
:
config/routes.rb
Rails.application.routes.draw do
get 'page/index'
root 'page#index'
end
We start the rails development server:
rails s
Fire up the browser and open http://0.0.0.0:3000
We use a User
model to store the user information and the password digest. We do not store the password in clear text in the database.
rails g scaffold User first_name last_name email password:digest
rake db:migrate
In the User
model we add a couple of validations and a to_s
method.
app/models/user.rb
class User < ActiveRecord::Base
has_secure_password
validates :first_name,
presence: true
validates :last_name,
presence: true
validates :email,
presence: true,
uniqueness: true,
format: {
with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
}
def to_s
"#{first_name} #{last_name}"
end
end
We create the User “Jon Smith” with http://127.0.0.1:3000/users/new
Having a User
model is nice but we need a Session
mechanism to create a login
and logout
procedure. Because we live in a RESTful world login
would be new
plus create
and logout
would be destroy
in a Sessions
controller.
rails g controller sessions new create destroy
Now we create a couple of routes for this to work:
config/routes.rb
Rails.application.routes.draw do
resources :sessions, only: [:new, :create, :destroy]
get 'signup', to: 'users#new', as: 'signup'
get 'login', to: 'sessions#new', as: 'login'
get 'logout', to: 'sessions#destroy', as: 'logout'
resources :users
get 'page/index'
root 'page#index'
end
The login form is the new.html.erb
view for a new Session
.
app/views/sessions/new.html.erb
<h1>Log In</h1>
<%= form_tag sessions_path do %>
<div class="field">
<%= label_tag :email %><br>
<%= text_field_tag :email %>
</div>
<div class="field">
<%= label_tag :password %><br>
<%= password_field_tag :password %>
</div>
<div class="actions">
<%= submit_tag "Log In" %>
</div>
<% end %>
The Session
controller needs 3 actions:
With that we use the RESTful approach.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
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
render :new
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: 'Logged out!'
end
end
We need a mechanism to access the current_user
everywhere in the application. For that we add a current_user
method to the ApplicationController
and use helper_method
to provide a helper method for the views too.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def current_user
User.where(id: session[:user_id]).first
end
helper_method :current_user
end
To show a user if he/she is already logged in and if not where to login or where to sign up we add a little header HTML in application.html.erb
Additionally I add some code to show flash messages if there are any.
app/views/layouts/application.html.erb
<div id="user_header">
<% if current_user %>
Logged in as <%= current_user.email %>.
<%= link_to "Log Out", logout_path %>
<% else %>
<%= link_to "Sign Up", signup_path %> or
<%= link_to "Log In", login_path %>
<% end %>
</div>
<% flash.each do |key, value| %>
<%= content_tag(:div, class: "alert alert-#{key}") do %>
<p><%= value %></p>
<% end %>
<% end %>
Because we render the flash messages in application.html.erb
we can delete them in the following files:
<p id="notice"><%= notice %></p>
When ever a new user signs up it makes sense for him to be logged in right away. That’s easy done by setting the session session[:user_id] = @user.id
in the users_controller.rb
.
app/controllers/users_controller.rb
# POST /users
def create
@user = User.new(user_params)
if @user.save
session[:user_id] = @user.id
redirect_to root_url, notice: 'User was successfully created.'
else
render :new
end
end
In the next screencast I’ll show how to use this authentication to setup an authorization system which grands or denies access to specific users.