Authentication is an important part of almost any web application and there are several approaches to take. Thankfully some of these have been put together in plugins so we don’t have to reinvent the wheel.
There are two popular gems for authentication: One is named AuthLogic and JumpStartLab wrote up an iteration using it for the Merchant tutorial, but it is a little complicated for a Rails novice. You have to create several different models, controllers, and views manually. The documentation is kind of confusing, and I don’t think my tutorial is that much better. The second is called Devise, and while it’s the gold standard for Rails 3 applications, it is also really complicated.
Sorcery is a lightweight and straightforward authentication service gem. It strikes a good balance of functionality and complexity.
Sorcery is just a gem like any other useful package of Ruby code, so to use it in our Blogger application we’ll need to add the following lines to our Gemfile:
When specifying and installing a new gem you will need to restart your Rails Server
Then at your terminal, instruct Bundler to install any newly-required gems:
Note: For Windows, to avoid any issue - you should install bcrypt first with this command:
gem install bcrypt --platform=ruby
Once you’ve installed the gem via Bundler, you can test that it’s available with this command at your terminal:
Somewhere in the middle of the output you should see the following:
If it’s there, you’re ready to go!
This plugin makes it easy to get up and running by providing a generator that creates a model representing our user and the required data migrations to support authentication. Although Sorcery provides options to support nice features like session-based “remember me”, automatic password-reset through email, and authentication against external services such as Twitter, we’ll just run the default generator to allow simple login with an email and password.
One small bit of customization we will do is to rename the default model created by Sorcery from “User” to “Author”, which gives us a more domain-relevant name to work with. Run this from your terminal:
rails generate sorcery:install --model=Author
Take a look at the output and you’ll see roughly the following:
Let’s look at the SorceryCore migration that the generator created before we migrate the database. If you wanted your User models to have any additional information (like “department_name” or “favorite_color”) you could add columns for that, or you could create an additional migration at this point to add those fields.
For this tutorial, you will need to add the username column to the Author model. To do that, open the migration file
*_sorcery_core.rb file under
db/migrate and make sure your file looks like this:
class SorceryCore < ActiveRecord::Migration
So go to your terminal and enter:
Let’s see what Sorcery created inside of the file
class Author < ActiveRecord::Base
We can see it added a declaration of some kind indicating our Author class authenticates via the sorcery gem. We’ll come back to this later.
First, stop then restart your server to make sure it’s picked up the newly generated code.
Though we could certainly drop into the Rails console to create our first user, it will be better to create and test our form-based workflow by creating a user through it.
We don’t have any create, read, update, and destroy (CRUD) support for our Author model. We could define them again manually as we did with Article. Instead we are going to rely on the Rails code controller scaffold generator.
rails generate scaffold_controller Author username:string email:string password:password password_confirmation:password
Rails has two scaffold generators: scaffold and scaffold_controller. The scaffold generator generates the model, controller and views. The scaffold_controller will generate the controller and views. We are generating a scaffold_controller instead of scaffold because Sorcery has already defined for us an Author model.
As usual, the command will have printed all generated files.
The generator did a good job generating most of our fields correctly, however, it did not know that we want our password field and password confirmation field to use a password text entry. So we need to update the
When we created the controller and the views we provided a
password field and a
password_confirmation field. When an author is creating their account we want to ensure that they do not make a mistake when entering their password, so we are requiring that they repeat their password. If the two do not match, we know our record should be invalid, otherwise the user could have mistakenly set their password to something other than what they expected.
To provide this validation when an author submits the form we need to define this relationship within the model (
class Author < ActiveRecord::Base
password_confirmation fields are sometimes referred to as “virtual attributes” because they are not actually being stored in the database. Instead, Sorcery uses the given password along with the automatically generated
salt value to create and store the
Visiting http://localhost:3000/authors at this moment we will find a routing error. The generator did not add a resource for our Authors. We need to update our
With this in place, we can now go to
http://localhost:3000/authors/new and we should see the new user form. Let’s enter in “firstname.lastname@example.org” for email, and “password” for the password and password_confirmation fields, then click “Create Author”. We should be taken to the show page for our new Author user.
Now it’s displaying the password and password_confirmation text here, lets delete that! Edit your
app/views/authors/show.html.erb page to remove those from the display.
If you click Back, you’ll see that the
app/views/authors/index.html.erb page also shows the password and password_confirmation. Edit the file to remove these as well.
We can see that we’ve created a user record in the system, but we can’t really tell if we’re logged in. Sorcery provides a couple of methods for our views that can help us out:
current_user method will return the currently logged-in user if one exists and
nil otherwise, and
true if a user is logged in and
false if not.
app/views/layouts/application.html.erb and add a little footer so the whole
<body> chunk looks like this:
The go to http://localhost:3000/articles/ and you should see “Logged out” on the bottom of the page.
How do we log in to our Blogger app? We can’t yet! We need to build the actual endpoints for logging in and out, which means we need controller actions for them. We’ll create an AuthorSessions controller and add in the necessary actions: new, create, and destroy.
First, let’s generate the AuthorSessions controller:
rails generate controller AuthorSessions
Now we’ll add
destroy methods to
class AuthorSessionsController < ApplicationController
As is common for Rails apps, the
new action is responsible for rendering the related form, the
create action accepts the submission of that form, and the
destroy action removes a record of the appropriate type. In this case, our records are the Author objects that represent a logged-in user.
Let’s create the template for the
new action that contains the login form, in
create action handles the logic for logging in, based on the parameters passed from the rendered form: email and password. If the login is successful, the user is redirected to the articles index, or if the user had been trying to access a restricted page, back to that page. If the login fails, we’ll re-render the login form. The
destroy action calls the
logout method provided by Sorcery and then redirects.
Next we need some routes so we can access those actions from our browser. Open up
config/routes.rb and make sure it includes the following:
resources :author_sessions, only: [ :new, :create, :destroy ]
Our Author Sessions are similar to other resources in our system. However, we only want to open a smaller set of actions. An author is able to be presented with a login page (:new), login (:create), and logout (:destroy). It does not make sense for it to provide an index, or edit and update session data.
The last two entries create aliases to our author sessions actions.
Externally we want our authors to visit pages that make the most sense to them:
Internally we also want to use path and url helpers that make the most sense:
- login_path, login_url
- logout_path, logout_url
Now we can go back to our footer in
and update it to include some links:
Now we should be able to log in and log out, and see our status reflected in the footer. Let’s try this a couple of times to confirm we’ve made it to this point successfully.
(You may need to restart the rails server to successfully log in.)
It looks like we can create a new user and log in as that user, but I still want to make some more changes. We’re just going to use one layer of security for the app – a user who is logged in has access to all the comments and pages, while a user who isn’t logged in can only post comments and try to login. But that scheme will breakdown if just anyone can go to this URL and create an account, right?
Let’s add in a protection scheme like this to the new users form:
- If there are zero users in the system, let anyone access the form
- If there are more than zero users registered, only users already logged in can access this form
That way when the app is first setup we can create an account, then new users can only be created by a logged in user.
We can create a
before_action which will run before the
create actions of our
authors_controller.rb. Open that controller and put all this code in:
before_action :zero_authors_or_authenticated, only: [:new, :create]
The first line declares that we want to run a before action named
zero_authors_or_authenticated when either the
create methods are accessed. Then we define that action, checking if there are either zero registered users OR if there is a user already logged in. If neither of those is true, we redirect to the root path (our articles list) and return false. If either one of them is true this action won’t do anything, allowing the requested user registration form to be rendered.
With that in place, try accessing
authors/new when you’re logged in and when you’re logged out. If you want to test that it works when no users exist, try this at your console:
Then try to reach the registration form and it should work! Create yourself an account if you’ve destroyed it.
The first thing we need to do is sprinkle
before_action on most of our controllers:
authors_controller, add a before action to protect the actions besides
before_action :require_login, except: [:new, :create]
author_sessions_controllerall the methods need to be accessible to allow login and logout
tags_controller, we need to prevent unauthenticated users from deleting the tags, so we protect just
destroy. Since this is only a single action we can use
before_action :require_login, only: [:destroy]
comments_controller, we never implemented
destroy, but just in case we do let’s allow unauthenticated users to only access
before_action :require_login, except: [:create]
articles_controllerauthentication should be required for
destroy. Figure out how to write the before action using either
Now our app is pretty secure, but we should hide all those edit, destroy, and new article links from unauthenticated users.
app/views/articles/show.html.erb and find the section where we output the “Actions”. Wrap that whole section in an
if clause like this:
<% if logged_in? %>
Look at the article listing in your browser when you’re logged out and make sure those links disappear. Then use the same technique to hide the “Create a New Article” link. Similarly, hide the ‘delete’ link for the tags index.
Your basic authentication is done, and the “Authentication” tutorial is complete!
We now have the concept of authenticated users, represented by our
Author class, in our blogging application, and it’s authors who are allowed to create and edit articles. What could be done to make the ownership of articles more explicit and secure, and how could we restrict articles to being edited only by their original owner?
git add .
That is the last commit for this project! If you would like to review your previous commits, you can do so with the git log command in terminal: