Laragon

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:

  1. AuthLogic - Used in the Merchant tutorial but can be complicated for Rails novices
  2. 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:

  1. Restart your Rails server
  2. 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]

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.