Authentication
Learn how to add user authentication to your Rails app using Sorcery; a lightweight, easy-to-use gem with just the right features.
Authentication is an important part of almost any web application. There are several approaches to take, with some available as plugins to avoid reinventing the wheel.
Authentication Gems
There are two popular gems for authentication in Rails:
- AuthLogic - Used in the Merchant tutorial but can be complicated for Rails novices
- Devise - The gold standard for Rails applications but also complex
Sorcery
Sorcery is a lightweight and straightforward authentication service gem that strikes a good balance of functionality and complexity.
Installing Sorcery
Add these lines to your Gemfile:
gem 'sorcery'
gem 'bcrypt'
After adding the gems:
- Restart your Rails server
- Install the gems with Bundler:
bundle
For Windows users, install bcrypt first:
gem install bcrypt --platform=ruby
Verify installation by running:
rails generate
You should see Sorcery in the output.
Running the Generator
To set up authentication with a custom model name (Author instead of User):
rails generate sorcery:install --model=Author
This creates:
- Initializer file
- Author model
- Migration file
Migration Customization
Edit the migration file (db/migrate/*_sorcery_core.rb
) to include a username field:
class SorceryCore < ActiveRecord::Migration
def change
create_table :authors do |t|
t.string :username, null: false
t.string :email, null: false
t.string :crypted_password, null: false
t.string :salt, null: false
t.timestamps
end
add_index :authors, :email, unique: true
end
end
Run the migration:
rake db:migrate
Creating User Accounts
Generate Controller and Views
rails generate scaffold_controller Author username:string email:string password:password password_confirmation:password
Update the form partial (app/views/authors/_form.html.erb
) to use password fields:
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>
Add Model Validation
Update app/models/author.rb
:
class Author < ActiveRecord::Base
authenticates_with_sorcery!
validates_confirmation_of :password, message: "should match confirmation", if: :password
end
Add Routes
Update config/routes.rb
:
resources :authors
Login/Logout Functionality
Generate Sessions Controller
rails generate controller AuthorSessions
Update app/controllers/author_sessions_controller.rb
:
class AuthorSessionsController < ApplicationController
def new
end
def create
if login(params[:email], params[:password])
redirect_back_or_to(articles_path, notice: 'Logged in successfully.')
else
flash.now.alert = "Login failed."
render action: :new
end
end
def destroy
logout
redirect_to(:authors, notice: 'Logged out!')
end
end
Create Login Form
app/views/author_sessions/new.html.erb
:
<h1>Login</h1>
<%= form_tag author_sessions_path, method: :post do %>
<div class="field">
<%= label_tag :email %>
<%= text_field_tag :email %>
<br/>
</div>
<div class="field">
<%= label_tag :password %>
<%= password_field_tag :password %>
<br/>
</div>
<div class="actions">
<%= submit_tag "Login" %>
</div>
<% end %>
<%= link_to 'Back', articles_path %>
Add Routes for Sessions
Update config/routes.rb
:
resources :author_sessions, only: [:new, :create, :destroy]
get 'login' => 'author_sessions#new'
get 'logout' => 'author_sessions#destroy'
Add Login/Logout Status
Update app/views/layouts/application.html.erb
:
<body>
<p class="flash">
<%= flash.notice %>
</p>
<div id="container">
<div id="content">
<%= yield %>
<hr>
<h6>
<% if logged_in? %>
<%= "Logged in as #{current_user.email}" %>
<%= link_to "(logout)", logout_path %>
<% else %>
<%= link_to "(login)", login_path %>
<% end %>
</h6>
</div>
</div>
</body>
Security Enhancements
Protect User Registration
In app/controllers/authors_controller.rb
:
before_action :zero_authors_or_authenticated, only: [:new, :create]
def zero_authors_or_authenticated
unless Author.count == 0 || current_user
redirect_to root_path
return false
end
end
Controller Protection
Add these before actions:
-
AuthorsController:
before_action :require_login, except: [:new, :create]
-
TagsController:
before_action :require_login, only: [:destroy]
-
CommentsController:
before_action :require_login, except: [:create]
-
ArticlesController:
before_action :require_login, except: [:index, :show]
Hide Protected Links
Wrap edit/delete links in conditional statements:
<% if logged_in? %>
<!-- protected links here -->
<% end %>
Extra Credit
Consider implementing:
- Explicit article ownership
- Restrict editing to original authors
- Additional user roles and permissions
Final Commit
git add .
git commit -m "Sorcery authentication complete"
git push
Congratulations! You've implemented authentication in your Rails application using Sorcery.