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 totrue
, an email with an activation link will not be sent automatically, allowing you to decide when to send it. Default value isfalse
.prevent_non_active_users_to_login
– whether non-activated users should be able to log in. Default isfalse
.
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 linkactivation_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 inlock_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
anduser.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.
Frequently Asked Questions (FAQs) about Magical Authentication with Sorcery
How does Sorcery compare to other authentication systems like Devise?
Sorcery is a lightweight, flexible authentication system for Rails applications. Unlike Devise, which is a full-featured solution, Sorcery provides only the core features for authentication, allowing developers to add additional functionality as needed. This makes it a great choice for developers who want more control over their authentication system. Sorcery is also easier to customize and extend, making it a good fit for complex applications.
Can I use Sorcery for passwordless authentication?
Yes, you can use Sorcery for passwordless authentication. While the default setup of Sorcery involves password-based authentication, it can be customized to support passwordless authentication. This involves sending a unique, temporary link to the user’s email address, which they can use to log in. This can be a more user-friendly approach to authentication, especially for mobile users.
How do I set up Sorcery in my Rails application?
Setting up Sorcery in a Rails application involves adding the Sorcery gem to your Gemfile, running the bundle install command to install it, and then running the Sorcery generator to create the necessary files. You’ll also need to configure Sorcery in your application’s initializer file, and add the necessary routes, controllers, and views for authentication.
How do I customize the authentication process with Sorcery?
Sorcery provides a number of configuration options that allow you to customize the authentication process. These include options for password encryption, session management, and user activation. You can also add additional modules to Sorcery to support features like remember me, reset password, and user activation.
How do I test authentication with Sorcery?
Testing authentication with Sorcery can be done using Rails’ built-in testing tools, along with libraries like RSpec and Capybara. Sorcery provides helper methods that make it easy to log in and out users in your tests, and to test the various aspects of the authentication process.
Can I use Sorcery with other Ruby frameworks?
While Sorcery was designed for Rails, it can also be used with other Ruby frameworks that support Rack middleware, such as Sinatra. This makes it a versatile choice for Ruby developers, regardless of their preferred framework.
How do I handle password resets with Sorcery?
Sorcery provides a reset password module that makes it easy to handle password resets. This involves generating a unique, temporary token for the user, which they can use to reset their password. The module also provides methods for sending the reset password email, and for updating the user’s password.
How secure is Sorcery?
Sorcery is designed to be secure, with features like password encryption, session timeout, and brute force protection. However, like any authentication system, its security depends on how it’s used. It’s important to follow best practices for authentication, such as using secure passwords, protecting against CSRF attacks, and keeping your application’s code and dependencies up to date.
Can I use Sorcery for multi-factor authentication?
While Sorcery doesn’t provide built-in support for multi-factor authentication, it can be extended to support it. This involves adding an additional step to the authentication process, where the user is required to provide a second form of identification, such as a code sent to their mobile device.
How do I handle user activation with Sorcery?
Sorcery provides a user activation module that makes it easy to handle user activation. This involves sending a unique, temporary link to the user’s email address, which they can use to activate their account. The module also provides methods for sending the activation email, and for activating the user’s account.
Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.