-
Notifications
You must be signed in to change notification settings - Fork 229
Simple Password Authentication
In this tutorial we will add Sorcery as the registration/authentication engine to Rails 4 app. At the end of the tutorial we will be able to register a user, and then login and logout with a username and a password.
Let's start from adding the sorcery gem into the Gemfile, and 'bundle install'. In the app's Gemfile:
# Gemfile
gem 'sorcery'
We'll start building the app by running this generator added by sorcery:
rails g sorcery:install
It will create the initializer file, the User model, unit test stubs, and the default migration, which we want to run now:
rake db:migrate
To get some views and controllers fast, we'll run rails scaffold generator, and skip the files we already created (we'll need to delete the new migration manually though):
rails g scaffold_controller user email:string crypted_password:string salt:string
We don't want users to edit/view their crypted password or salt, so we'll remove these from all templates in app/views/users/.
We also need to allow UsersController receive form attributes, and to remove crypted password and salt from user_params
:
class UsersController < ApplicationController
# ...
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
We'll need to add a password 'virtual' field instead, that will hold the password before it is encrypted into the database:
# app/views/users/_form.html.erb
<div class="field">
<%= form.label :password %><br />
<%= form.password_field :password %>
</div>
<div class="field">
<%= form.label :password_confirmation %><br />
<%= form.password_field :password_confirmation %>
</div>
The virtual attributes will be added via validates :password, confirmation: true
as seen below. You may also want to allow the user to change only some of his attributes. Also we need to let the User model know it is using sorcery. We do that like this:
# app/models/user.rb
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }
validates :email, uniqueness: true
end
And we need to define routes.
# config/routes.rb
root :to => 'users#index'
resources :users
Now run the app and create a new user at http://0.0.0.0:3000/users. Voila! The password was automatically encrypted, and a salt was also auto-created! By default the encryption algorithm used is BCrypt (using the bcrypt-ruby gem) but that can be configured, as well as the salt, and the database field names.
Now we need a way to login after registering, so we'll generate a controller for that:
rails g controller UserSessions new create destroy
Make it look like this:
# app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
def create
@user = login(params[:email], params[:password])
if @user
redirect_back_or_to(:users, notice: 'Login successful')
else
flash.now[:alert] = 'Login failed'
render action: 'new'
end
end
def destroy
logout
redirect_to(:users, notice: 'Logged out!')
end
end
'login' takes care of login, 'logout' takes care of logout. 'redirect_back_or_to' takes care of redirecting the user to the page he asked for before reaching the login form, if such a page exists.
Let's create the login form:
# app/views/user_sessions/new.html.erb
<h1>Login</h1>
<%= render 'form' %>
<%= link_to 'Back', users_path %>
# app/views/user_sessions/_form.html.erb
<%= form_with url: login_path, method: :post do |f| %>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="actions">
<%= f.submit "Login" %>
</div>
<% end %>
And define some routes. At this point make sure your routes.rb file includes these routes and remove others:
# config/routes.rb
root :to => 'users#index'
resources :users
get 'login' => 'user_sessions#new', :as => :login
post 'login' => "user_sessions#create"
post 'logout' => 'user_sessions#destroy', :as => :logout
One Last thing, we need some navigation links, and a way to display flash messages:
# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Tutorial</title>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div id="nav">
<% if current_user %>
<%= link_to "Edit Profile", edit_user_path(current_user.id) %>
<%= link_to "Logout", :logout, method: :post %>
<% else %>
<%= link_to "Register", new_user_path %> |
<%= link_to "Login", :login %>
<% end %>
</div>
<div>
<p id="notice"><%= flash[:notice] %></p>
<p id="alert"><%= flash[:alert] %></p>
</div>
<%= yield %>
</body>
</html>
Now you can click on the "Login" link, enter your username and password, and get redirected to the users list with a nice flash message. Try the "Logout" link as well.
OK, that's nice, but our logged-out users can do anything a logged-in user can, like editing users for example. Let's fix that by protecting our sensitive controller actions:
# app/controllers/application_controller.rb
before_action :require_login
# app/controllers/users_controller.rb
skip_before_action :require_login, only: [:index, :new, :create] # this should only be used if you are allowing users to register themselves.
# app/controllers/user_sessions_controller.rb
skip_before_action :require_login, only: [:new, :create]
The default controller code redirects you to "show" action after "create" completes. This means when you register a user, you will be redirected to a protected action, so let's fix it by redirecting to the users index action:
# app/controllers/users_controller.rb
...
if @user.save
redirect_to(:users, notice: 'User was successfully created')
...
# app/controllers/application_controller.rb
private
def not_authenticated
redirect_to login_path, alert: "Please login first"
end
Now some parts of the application are protected from logged-out users. Also, if you try to edit a user while logged out, get denied and then login at the login form, you'll see you are automatically taken to the edit page. If 'not_authenticated' is a problematic name for you, it is possible to supply a different method at configuration time, that will be called when a user is denied due to being logged-out.
Meta
Using Sorcery
- Activity Logging
- Brute Force Protection
- DataMapper Support
- DelayedJob Integration
- Distinguish login failure reasons
- External
- External---Microsoft-Graph-authentication
- Fetching Currently Active Users
- HTTP Basic Auth
- Integration Testing
- OAuth Landing Page
- Password-less Activation
- Remember Me
- Reset Password
- Routes Constraints
- Session Timeout
- Simple Password Authentication
- Single Table Inheritance Support
- Testing Rails
- User Activation
Contributing to Sorcery