Ruby
Article

Magical Authentication with Sorcery

By Ilya Bodrov-Krukowski

Authentication in Rails

Magician Hat

Authentication is a vital part of many web apps. I should not need to persuade you that an authentication must be easy to use and well-protected at the same time. You may either write it from scratch or use one of the many gems available out there.

This article is the first in the upcoming series devoted to authentication in Rails. We are going to take a look at Sorcery, a less well-known, but very convenient and easy to use gem created by Noam Ben-Ari and other folks. In comparison to Devise, Sorcery is a bit more low level and requires the developer to perform some additional actions. However, this is a good thing because you can cherry-pick only the required functionality.

Additional options are added with the help of submodules. Initially, Sorcery provides only the most minimal set of features. There are submodules to enable user activation, brute force protection, OAuth 2 support and more. It is really up to you to decide on what your app needs. “Less is more” is one of the main principles of Sorcery.

I hope you are excited to get to know Sorcery better. :) Read on and let’s build a demo app together!

The source code is available on GitHub.

The working demo is available at sitepoint-sorcery.herokuapp.com.

Preparing the App

I am going to call my demo app “Magical” – we’re integrating magic authentication after all:

$ rails new Magical -T

Rails 4.2.0 will be used, but the same solution can be implemented with Rails 3. This demo app will provide no functionality apart from authentication and related features, however we need at least one page for testing purposes that should be accessible only by authenticated users, so create a basic PagesController:

pages_controller.rb

class PagesController < ApplicationController
  def index
  end
end

the corresponding view:

views/pages/index.html.erb

<h1>Welcome!</h1>

<p>Restricted area for authorized users only.</p>

and add the routes:

config/routes.rb

[...]
get '/secret', to: 'pages#index', as: :secret
root to: 'pages#index'
[...]

I am also going to use Twitter Bootstrap to style the app a bit:

Gemfile

[...]
gem 'bootstrap-sass', '~> 3.3.3'
[...]

application.scss

@import "bootstrap-sprockets";
@import "bootstrap";
@import "bootstrap/theme";

.nav > li > span {
  display: block;
  padding-top: 15px;
  padding-bottom: 15px;
  color: #9d9d9d;
}

views/layouts/application.html.erb

[...]
<nav class="navbar navbar-inverse">
  <div class="container">
    <div class="navbar-header">
      <%= link_to 'Magical', root_path, class: 'navbar-brand' %>
    </div>
    <div id="navbar">
      <ul class="nav navbar-nav">
        <li><%= link_to 'Secret Page', secret_path %></li>
      </ul>
    </div>
  </div>
</nav>

<div class="container">
  <% flash.each do |key, value| %>
    <div class="alert alert-<%= key %>">
      <%= value %>
    </div>
  <% end %>

  <%= yield %>
</div>
[...]

Okay, preparations are done…it was fast, wasn’t it? Now, let’s integrate Sorcery into the app!

Integrating Sorcery

Model and migrations

First of all, drop the gem into your Gemfile:

Gemfile

[...]
gem 'sorcery'
[...]

and run

$ bundle install

Sorcery’s integration should not raise any difficulties, however there are a couple of known incompatibilities that you should take into consideration.

We need to generate Sorcery’s config and migration. Initially Sorcery provides only the most basic functionality – authentication itself. No brute force protection, no “remember me”, no “restore password”, not even email validity checking. This, however, means that you can cherry-pick only the features that you really need.

Go ahead and run the command:

$ rails g sorcery:install

This will create the config/initializers/sorcery.rb file, User model, and migration. The User is given three fields by Sorcery:

  • crypted_password (string)
  • salt (string)
  • email (string)

If you wish to specify a different name for the model, provide the --model flag:

$ rails g sorcery:install --model Admin

Open up the newly generated migration file and add the following line inside the create_table method:

xxx_sorcery_core.rb

[...]
t.string :name
[...]

This way we are ensuring that users can also provide their names. The migration can now be applied:

$ rake db:migrate

On to the model. If you open the models/user.rb file, you’ll notice that the

models/user.rb

[...]
authenticates_with_sorcery!
[...]

line is present there. It adds some Sorcery’s methods to the model. What we have to do here is add some validations, because initially none are present. This means that any email and password (including blank) may be provided.

models/user.rb

[...]
validates :password, length: { minimum: 3 }
validates :password, confirmation: true
validates :email, uniqueness: true
[...]

I am also requiring a minimum length to the password, requiring a confirmation, and making sure email is unique.

How about email format? You might write your own regular expression, but it appears that this regexp is crazy long. Instead, grab an existing gem to solve this problem:

Gemfile

[...]
gem 'validates_email_format_of'
[...]

Don’t forget to run

$ bundle install

Then modify the model like this:

models/user.rb

[...]
validates :email, uniqueness: true, email_format: { message: 'has invalid format' }
[...]

Now we can be sure that email have the correct format. It is high time to proceed to controllers.

Sign Up

Two controllers will be needed: one to handle user registrations (and possibly profile updating or deletion) and another one to handle logging in and out.

Let’s start with registrations. Call this controller UsersController:

users_controller.rb

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = 'Welcome!'
      redirect_to root_path
    else
      render 'new'
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation, :name)
  end
end

The view:

views/users/new.html.erb

<h1>Registration</h1>

<%= form_for @user do |f| %>
  <%= render 'shared/errors', object: @user %>
  <div class="form-group">
    <%= f.label :name %>
    <%= f.text_field :name, class: 'form-control', required: true %>
  </div>

  <div class="form-group">
    <%= f.label :email %>
    <%= f.email_field :email, class: 'form-control', required: true %>
  </div>
  <div class="form-group">
    <%= f.label :password %>
    <%= f.password_field :password, class: 'form-control', required: true %>
  </div>
  <div class="form-group">
    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation, class: 'form-control', required: true %>
  </div>
  <%= f.submit 'Register', class: 'btn btn-primary btn-lg' %>
<% end %>

Sorcery uses the bcrypt-ruby gem to secure passwords. This means that password are never stored in plain text – only their digest is present in the database ( in the crypted_password field). Therefore, password and password_confirmation are virtual attributes without corresponding table fields.

You may also have noted the salt field – it is a random string used to further protect the password. You see, digests generated by hash functions are nearly impossible to invert and restore to the original message. What can be done, however, is generating a dictionary of words and the corresponding digests. As long as the same message always has the same digest, an attacker who gained access to the database can search digest through the dictionary and look up the corresponding string. The salt is meant to prevent such attacks. It is generated when a user is first created and used in hashing process like this:

hash(salt + password)

It is impossible to build a dictionary for each unique salt, so the password becomes truly protected. You may read more here.

By the way, you may use other encryption algorithms by overriding the user.encryption_algorithm = option inside the config/initializers/sorcery.rb file.

Let’s add the routes:

config/routes.rb

[...]
resources :users, only: [:new, :create]
get '/sign_up', to: 'users#new', as: :sign_up
[...]

Okay, now users may sign up, but they will not be logged in by default. To do that the login method can be used that accepts at least two arguments: email and password. Add it to the controller:

users_controller.rb

[...]
def create
  @user = User.new(user_params)
  if @user.save
    login(params[:user][:email], params[:user][:password])
[...]

Try to sign up…everything should be working fine.

Log In and Out

The next step is implementing functionality for logging in and out. Create a new
SessionsController:

sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end

  def create
    if login(params[:email], params[:password])
      flash[:success] = 'Welcome back!'
      redirect_to root_path
    else
      flash.now[:warning] = 'E-mail and/or password is incorrect.'
      render 'new'
    end
  end

  def destroy
    logout
    flash[:success] = 'See you!'
    redirect_to log_in_path
  end
end

The same login method is used in the create action. If the user has provided an incorrect email or password, this method returns nil, so an error message would be shown. The logout method inside the destroy action does pretty much what it says – logs the user out.

Now the view:

views/sessions/new.html.erb

<h1>Log In</h1>
<%= form_tag sessions_path, method: :post do %>
  <div class="form-group">
    <%= label_tag :email %>
    <%= email_field_tag :email, nil, class: 'form-control', required: true %>
  </div>

  <div class="form-group">
    <%= label_tag :password %>
    <%= password_field_tag :password, nil, class: 'form-control', required: true %>
  </div>

  <%= submit_tag 'Log In', class: 'btn btn-primary btn-lg' %>
<% end %>

And the routes:

config/routes.rb

[...]
resources :sessions, only: [:new, :create, :destroy]
get '/log_in', to: 'sessions#new', as: :log_in
delete '/log_out', to: 'sessions#destroy', as: :log_out
[...]

With this in place, it’s time to tweak the layout to display all the necessary links.

Displaying the Links

We want to present “Sign Up” and “Log In” links to users who are not currently authenticated. “Log Out” and “Secret Page” links should be shown once they sign in. To check whether the user is authenticated or not, use the current_user method, which returns a user record or nil:

views/layouts/application.html.erb

[...]
<div id="navbar">
  <ul class="nav navbar-nav">
    <% if current_user %>
      <li><%= link_to 'Secret Page', secret_path %></li>
    <% else %>
      <li><%= link_to 'Sign Up', sign_up_path %></li>
      <li><%= link_to 'Log In', log_in_path %></li>
    <% end %>
  </ul>
  <% if current_user %>
    <ul class="nav navbar-nav pull-right">
      <li><span><%= current_user.name %></span></li>
      <li><%= link_to 'Log Out', log_out_path, method: :delete %></li>
    </ul>
  <% end %>
</div>
[...]

This is pretty straightforward, isn’t it? However, the secret page still can be accessed just by typing its URL directly. What we need to do is add some kind of check in the page controller.

Restricting Access

Luckily, Sorcery provides a require_login method that restricts certain pages from unauthorized users. It should be used as a before_action like this:

application_controller.rb

[...]
before_action :require_login
[...]

However this would result in restricting access to all pages. Obviously, users who are not logged in should be able to visit Log In and Sign Up pages. Thus, use skip_before_action:

users_controller.rb

[...]
skip_before_action :require_login, only: [:new, :create]
[...]

sessions_controller.rb

[...]
skip_before_action :require_login, except: [:destroy]
[...]

How about the action that happens when an unauthenticated user tries to open a restricted page? Open Sorcery’s initializer file:

config/initializers/sorcery.rb

[...]
# What controller action to call for non-authenticated users. You can also
# override the 'not_authenticated' method of course.
# Default: `:not_authenticated`
#
# config.not_authenticated_action =
[...]

The not_authenticated method is what we need. Create it:

application_controller.rb

[...]
private

def not_authenticated
  flash[:warning] = 'You have to authenticate to access this page.'
  redirect_to log_in_path
end
[...]

There is one more small improvement that may be added. Currently, when an unauthenticated user opens a restricted page, they will be redirected to the log in page. After logging in, they’re taken to the main page of the site. This is not very convenient. It would be better to redirect a user to the page they were trying to see. Meet the redirect_back_or_to method. This method will either redirect the user back to where they came from or to the specified page:

sessions_controller.rb

[...]
def create
  if login(params[:email], params[:password])
    flash[:success] = 'Welcome back!'
    redirect_back_or_to root_path
[...]

Do You Still Remember Me?

You’re probably used to the little checkbox labeled “remember me” on sign in pages. How about adding it to out app as well?

We can take advantage of Sorcery’s submodules for this task. Each submodule provides its own piece of functionality and can be hooked up independently. Think of them as of Lego blocks.

The submodule that we are currently interested in is called RememberMe. Run the following command to copy the required migration:

$ rails g sorcery:install remember_me  --only-submodules

In some documentation, you might find the --migrations flag instead of --only-submodules but the latter one is preferred, as --migrations is deprecated. This migration will add two columns to the users table:

  • remember_me_token (string)
  • remember_me_token_expires_at (datetime)

This token is used to “remember” the user and, obviously, it should not be valid forever. Apply the migration:

$ rake db:migrate

Now, register the new submodule:

config/initializers/sorcery.rb

[...]
Rails.application.config.sorcery.submodules = [:remember_me]
[...]

If you wish to tweak the duration of the token, look for the user.remember_me_for option in the sorcery.rb file. The default value is one week.

Add the checkbox to the log in form:

views/sessions/new.html.erb

[...]
<%= form_tag sessions_path, method: :post do %>
  [...]
  <div class="form-group">
    <%= label_tag :remember_me %>
    <%= check_box_tag :remember_me %>
  </div>
  [...]
<% end %>

Lastly, tweak the corresponding controller’s method:

sessions_controller.rb

[...]
def create
  if login(params[:email], params[:password], params[:remember_me])
    flash[:success] = 'Welcome back!'
    redirect_back_or_to root_path
  else
    flash.now[:warning] = 'E-mail and/or password is incorrect.'
    render 'new'
  end
end
[...]

The login method can also accept the third optional parameter specifying if the user should be remembered or not.

Activate Yourself!

On many websites, the user has to activate their account by visiting a link sent to them via email before actually logging in. Let’s implement the same feature. Install a new submodule:

$ rails g sorcery:install user_activation  --only-submodules

and apply the migration:

$ rake db:migrate

That is going to add the following fields:

  • activation_state (string)
  • activation_token (string)
  • activation_token_expires_at (datetime)

Register the submodule:

config/initializers/sorcery.rb

[...]
Rails.application.config.sorcery.submodules = [:user_activation]
[...]

Also, tweak the settings:

config/initializers/sorcery.rb

[...]
user.user_activation_mailer = UserMailer
[...]

There are also a couple of settings worth mentioning:

  • user.activation_mailer_disabled – if set to true, an email with an activation link will not be sent automatically, allowing you to decide when to send it. Default value is false.
  • prevent_non_active_users_to_login – whether non-activated users should be able to log in. Default is false.

Generate the mailer to handle sending the email:

$ rails g mailer UserMailer activation_needed_email activation_success_email

Delete all the .text.erb files from the layouts and user_mailer directory. Now modify the mailer like this:

mailers/user_mailer.rb

class UserMailer < ApplicationMailer
  def activation_needed_email(user)
    @user = user
    mail(to: user.email, subject: "Account activation")
  end

  def activation_success_email(user)
    @user = user
    mail(to: user.email, subject: "Your account is now activated")
  end
end

As you can see, Sorcery requires two methods:

  • activation_needed_email sends an email with the activation link
  • activation_success_email sends a confirmation email saying the account was activated successfully.

Actually, you can disable sending the “successful” email by setting activation_success_email_method_name in sorcery.rb to nil.

The views:

views/user_mailer/activation_needed_email.html.erb

<p>Welcome, <%= @user.name %>!</p>

<p>To login to the site, just follow <%= link_to 'this link', activate_user_url(@user.activation_token) %>.</p>

<p>Thanks for joining and have a great day!</p>

views/user_mailer/activation_success_email.html.erb

<p>Congratulations, <%= @user.name %>!</p>

<p>You have successfully activated your account.</p>

<p>You may now proceed to <%= link_to 'log in page', log_in_url %>.</p>

<p>Thanks for joining and have a great day!</p>

For link helpers to work we will need to do some configuration:

config/environments/development.rb

[...]
config.action_mailer.default_url_options = { host: '127.0.0.1:3000' }
[...]

Please note that emails won’t be actually sent in development – you will only be able to see their contents and meta information in the console. Set config.action_mailer.perform_deliveries to true in development.rb to change this behavior.

For production, you will have to configure the SMTP settings. Some examples can be found here. In my demo app, I am going to disable user activation.

To complete this step, we have to add a controller method and a route:

config/routes.rb

[...]
resources :users, only: [:new, :create] do
  member do
    get :activate
  end
end
[...]

users_controller.rb

[...]
skip_before_action :require_login

def activate
  if @user = User.load_from_activation_token(params[:id])
    @user.activate!
    flash[:success] = 'User was successfully activated.'
    redirect_to log_in_path
  else
    flash[:warning] = 'Cannot activate this user.'
    redirect_to root_path
  end
end
[...]

load_from_activation_token is a method presented by Sorcery that finds a resource by an activation token. activate! actually activates the account and saves the result to the database.

Awesome! You may now go check how this all is working.

Integrating with DelayedJob

You probably noticed that it takes some time for an email to be sent and the page won’t load until this operation is completed. This is not a good user experience, as sending the email might take a long time. To fix this, the email sending process should be made asynchronous.

DelayedJob by CollectiveIdea can be utilized to achieve this goal.

Drop in a new gem:

Gemfile

[...]
gem 'delayed_job_active_record'
# or
gem 'delayed_job_mongoid'
[...]

and run

$ bundle install

Generate and apply DelayedJob’s migration issue:

$ rails generate delayed_job:active_record
$ rake db:migrate

Also, add the following line to the application config to set the queueing backend:

config/application.rb

[...]
config.active_job.queue_adapter = :delayed_job
[...]

This should be done only for Rails 4.2+.

Lastly, place the following code at the end of Sorcery’s initializer file:

config/initializers/sorcery.rb

[...]
module Sorcery
  module Model
    module InstanceMethods
      def generic_send_email(method, mailer)
        config = sorcery_config
        mail = config.send(mailer).delay.send(config.send(method), self)
      end
    end
  end
end

The email sending is now performed asynchronously, yay!

To test this on the local machine, run

$ rake jobs:work

so that DelayedJob starts processing jobs and boot the server by issuing

$ rails s

in a separate console tab. Next register a new user and navigate to DelayedJob’s console tab. You will see something like

[Worker(host:xxx pid:7128)] Job UserMailer.send (id=1) RUNNING
[Worker(host:xxx pid:7128)] Job UserMailer.send (id=1) COMPLETED after 2.2951
[Worker(host:xxx pid:7128)] 1 jobs processed at 0.4057 j/s, 0 failed

This means that the integration was completed successfully.

Protecting from Brute Force

Brute force is probably the most widely known type of attack. Basically, the attacker tries to “guess” a password by trying one combination of symbols after another. To be secure, an account should be locked out after some unsuccessful log in attempts.

Add another Sorcery submodule:

$ rails g sorcery:install brute_force_protection  --only-submodules

This generates a migration that is going to add these columns:

  • failed_logins_count (integer) – how many times in a row has the user made unsuccessful attempts to log in
  • lock_expires_at (default) – when the account will be unlocked (if it is locked)
  • unlock_token (string) – a random token to unlock an account

Apply the migration:

$ rake db:migrate

Register the new module:

config/initializers/sorcery.rb

[...]
Rails.application.config.sorcery.submodules = [:brute_force_protection]
[...]

That’s it. Sorcery will take care of the rest, but you may want to tweak some settings, such as:

  • user.consecutive_login_retries_amount_limit – how many unsuccessful attempts are allowed. The default is 50.
  • user.login_lock_time_period – how long the user should be locked out. The default is 3600 seconds. Provide 0 to lock out the user indefinitely.
  • user.unlock_token_mailer and user.unlock_token_email_method_name – class and method to send emails to users with unlock tokens (by default they are not set up).

If you want to let the user unlock their account, apart from creating a mailer, you will need a separate controller (ResetPasswordsController, for example) with a method similar to this one:

[...]
def create
  u = User.load_from_unlock_token(params[:token])
  u.unlock!
  flash[:success] = 'Your account was unlocked!'
  redirect_to root_url
end
[...]

load_from_unlock_token is a method provided by Sorcery that searches for a user by the provided unlock token. unlock!, in turn, removes the lock. ! at the end of the method’s name says that the user will immediately be saved, so you don’t have to call u.save.

Log the Activity

Activity logging is a submodule that helps implement functionality like “who is currently online”. You know what to do:

$ rails g sorcery:install activity_logging  --only-submodules

that will add the following fields:

  • last_login_at (datetime)
  • last_logout_at (datetime)
  • last_activity_at (datetime)
  • last_login_from_ip_address (string)

Apply the migration:

$ rake db:migrate

Register the submodule:

config/initializers/sorcery.rb

[...]
Rails.application.config.sorcery.submodules = [:activity_logging]
[...]

We need a method that returns currently active users. Sorcery used to provide the current_users method, but it was decided to remove it. Therefore, let’s write our own version:

application_controller.rb

[...]
private

def current_users
  User.current_users
end

helper_method :current_users
[...]

user.rb

[...]
class << self
  def current_users
    where("#{sorcery_config.last_activity_at_attribute_name} IS NOT NULL") \
.where("#{sorcery_config.last_logout_at_attribute_name} IS NULL
  OR #{sorcery_config.last_activity_at_attribute_name} > #{sorcery_config.last_logout_at_attribute_name}") \
.where("#{sorcery_config.last_activity_at_attribute_name} > ? ", sorcery_config.activity_timeout.seconds.ago.utc.to_s(:db))
  end
end
[...]

There are some other examples on the Sorcery wiki.

Now, just use the current_users method to display user’s names:

views/layouts/application.html.erb

[...]
<div class="col-sm-9">
  <%= yield %>
</div>
<%= render 'shared/current_users' %>
[...]

views/shared/_current_users.html.erb

<div class="col-sm-3 well well-sm">
  <h3>Currently active users:</h3>
  <ul>
    <% current_users.each do |user| %>
      <li><%= user.name %></li>
    <% end %>
  </ul>
</div>

Great!

If you are interested, this module simply sets some callbacks that fire after login, before logout, and after every other request. These callbacks update the corresponding fields. For example, here is the code that runs after each user request.

Update 2015/04/01: Forgot Password

This update was, once again, inspired by one of the readers who asked me to show how to implement password resetting functionality with Sorcery. Thanks to all of you for such great feedback!

Users tend to forget their passwords, so presenting an option to reset them is absolutely necessary for any authentication system. With Sorcery, we can do this really fast.

Install the new ResetPassword module as always:

$ rails g sorcery:install reset_password --only-submodules

This will generate a migration adding the following fields to the users table:

  • reset_password_token (string, index)
  • reset_password_token_expires_at, (datetime)
  • reset_password_email_sent_at, (datetime)

reset_password_token is a random string that will be generated when password reset instructions are requested. This token is only valid for a limited period of time and is nullified as soon as the user changes his password. It will be later used to fetch a user record and change the password for it, so tokens should be kept safe.

Make sure that the module was registered:

config/initializers/sorcery.rb

[...]
    Rails.application.config.sorcery.submodules = [:reset_password]
    [...]

Now provide the “Forgot your password?” link:

views/sessions/new.html.erb

<p><%= link_to 'Forgot your password?', new_reset_password_>path %></p>

Set up some routes:

config/routes.rb

[...]
    resources :reset_passwords, only: [:new, :create, :update, :edit]
    [...]

and create a new controller:

reset_passwords_controller.rb

class ResetPasswordsController < ApplicationController
      skip_before_filter :require_login

      def new
      end
    end

Create the first view where the user will enter the e-mail to receive password reset instructions:

views/reset_passwords/new.html.erb

<h1>Reset Password</h1>

    <%= form_tag reset_passwords_path, method: :post do %>
      <div class="form-group">
        <%= label_tag :email %>
        <%= text_field_tag :email, nil, class: 'form-control', required: true %>
      </div>

      <%= submit_tag "Send reset instructions", class: 'btn btn-primary btn-lg' %>
    <% end %>

Now the create method:

reset_passwords_controller.rb

[...]
  def create
    @user = User.find_by_email(params[:email])
    @user.deliver_reset_password_instructions! if @user
    flash[:success] = 'Instructions have been sent to your email.'
    redirect_to log_in_path
  end
    [...]

deliver_reset_password_instructions! is the method provided by Sorcery that will actually rely on your own mailer to send password reset instructions. We already have the UserMailer in place, so let’s just add a new method there:

mailers/user_mailer.rb

[...]
  def reset_password_email(user)
    @user = user
    @url  = edit_reset_password_url(@user.reset_password_token)
    mail(to: user.email,
         subject: "Your password has been reset")
  end
    [...]

Add the corresponding view:

views/user_mailer/reset_password_email.html.erb

<p>Hello, <%= @user.name %>!</p>

    <p>Someone (hopefully, you) have requested to reset your password.</p>

    <p>To choose a new password, follow this link: <%= link_to @url, @url %>.</p>

We also have to provide the mailer’s name in Sorcery’s config file:

config/initializers/sorcery.rb

[...]
    user.reset_password_mailer = UserMailer
    [...]

You may override the user.reset_password_email_method_name if you don’t like the default reset_password_email method name.

Now add the edit action that will be called when a user clicks on the link provided in the e-mail:

reset_passwords_controller.rb

[...]
  def edit
    @token = params[:id]
    @user = User.load_from_reset_password_token(@token)

    not_authenticated if @user.blank?
  end
    [...]

and the view:

views/reset_passwords/edit.html.erb

<h1>Choose a new password</h1>

    <%= form_for @user, url: reset_password_path(@token), method: :patch do |f| %>
      <%= render 'shared/errors', object: @user %>

      <div class="form-group">
        <%= f.label :password %>
        <%= f.password_field :password, class: 'form-control', required: true %>
      </div>

      <div class="form-group">
        <%= f.label :password_confirmation %>
        <%= f.password_field :password_confirmation, class: 'form-control', required: true %>
      </div>

        <%= f.submit 'Set password', class: 'btn btn-primary btn-lg' %>
    <% end %>

Lastly, add the update action to handle password changing:

reset_passwords_controller.rb

[...]
  def update
    @token = params[:id]
    @user = User.load_from_reset_password_token(@token)

    not_authenticated && return if @user.blank?

    @user.password_confirmation = params[:user][:password_confirmation]
    if @user.change_password!(params[:user][:password])
      flash[:success] = 'Password was successfully updated.'
      redirect_to log_in_path
    else
      render "edit"
    end
  end
    [...]

And you’re done! Feel free modify this code further and post your questions if you’re in trouble.

Conclusion

We’ve taken a look at Sorcery’s basic setup and its submodules. There are some more of them available, so take a look at the project wiki to learn more.

Have you ever tried using Sorcery in your projects? Did you find it convenient? Share your experiences and don’t hesitate to post your questions in the comments.

Comments
Amit_Joki

That's a really great post. But you've missed an important part of authentication. That is, recovering passwords. Can you show how to do it? The module is here https://github.com/NoamB/sorcery/blob/master/lib/sorcery/model/submodules/reset_password.rb

bodrovis

Thank you! Okay, I will research this and make an update in some days smile

bodrovis

See the updated version of the article!

Amit_Joki

Great! BTW, I implemented it from going through the Sorcery's github repo. Thanks for responding anyway smile

Also it's worth noting that ActiveJob works only on 4.2+, so including that would be helpful as I spent 3 hours only to realise I was using Rails 4.1.8

Amit_Joki

Hey also, can you consider putting up an example of adding a profile picture using "Attachinary + Cloudinary"? Hoping that I am not asking too much stuck_out_tongue

The reason I'm asking for Attachinary is that it's much more streamlined and takes fewer lines of code with direct uploading to cloudinary.

They also say that no changes in the model is required. So, it would nicely integrate with this authentication system.

The link is here.. https://github.com/assembler/attachinary.

bodrovis

Huh, I'll look into this and maybe implement such functionality smile

frankis

Thank you for taking the time putting together this article - it is much of help and actually, I got the impression the sorcery is more what I need compared to devise - which simply seems to be "too much" - and who wants to maintain bloat at the end of the day?

However, there is two things I really apprecaite your feedback on as it can get critical pretty fast:

  1. On the second article (authentification using devise), you mentioned
    that, if running on heroku, at least one 1 worker instance (at
    $35) is needed - this article doesn´t mention that. Is that not
    required here with the setup or did you learn it the "hard way" (as
    the devise article is newer I assume the latter).
  2. If I plan to use authentification (standard as well google, facebook), then using fog for cloudservices such as S3 and pundit or CanCanCan for authorization (will have a lot different roles/groups), do you think sourcery would be "enough" (I prefer it as it seems to contain much less bloat in relation to devise were I doubt I would use 5% of its features)
  3. When it comes to authorization, do you think sorcery and CanCanCan play well together? I read your article about CanCanCan and liked it a lot. (Also having a question there :))

Would be great if you can take the time to answer or, if you feel good doing so, extend this real world example by using it with CanCanCan or pundit.

bodrovis

Thank you for the feedback!

  1. You need worker process only to perform background tasks, like sending e-mail asynchronously. I don't really remember how I learned that, as I am using Heroku for quite a long time smile If no such tasks are present, worker process is not needed (you can send e-mail synchronously, however this is not very user-friendly). Btw, Heroku has new pricing plans. https://www.heroku.com/beta-pricing
  2. Yes, I believe that would be enough, however I had not used such setup myself - rather build authentication from scratch and used CanCan.
  3. Yeah, Sorcery does not really care about authorization mechanism and CanCan does not care about authentication - it only requires current_user to be present.

Thank you again for kind words, I really appreciate that.

Fduch

Thank you for your impact to RoR community. You've done excellent job.

In addition to already shown information, could you please provide us with an example of AJAX integration for login and signup processes, using for example bootstrap modals?

And also, could you please explain inclusion of shared/errors in views/users/new.html.erb? You didn't mention it in your tutorial, but I see it in the source code.

Thank you in advance.

bodrovis

Thank you!

I am planning to do yet another article on authorization in some time and I'll consider using AJAX smile

Regarding the "errors" partial - yeah, I absolutely forgot to mention it. The actual code for it is on GitHub https://github.com/bodrovis/SitePoint-Sorcery/blob/master/app/views/shared/_errors.html.erb This is a pretty simple partial for rendering errors found while submitting the form. I prefer to do it this way because you may have many different forms and only one partial for errors. I can explain this in a more detail if it is needed.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Ruby, once a week, for free.