Clearance is thoughtbot’s authentication library for Rails applications.
We needed to verify that people signing up to our application owned the email address they signed up with. Clearance doesn’t provide an email confirmation step out of the box, but it does provide a simple and powerful API that we can use to add such behavior.
This post will follow step by step how to add email confirmation to a Rails application with Clearance in test-driven fashion.
Feature spec
We start modifying the relevant feature spec, to take into account the new functionality. We add a step right after sign up, where a user wants to sign in but is prevented with an error message about their email not being confirmed yet.
feature "User authentication" do
-   scenario "Visitor signs up and signs out" do
+   scenario "Visitor signs up, tries to sign in, confirms email and signs out" do
    visit root_path
    click_link t("labels.sign_up")
    fill_in "Email", with: "clarence@example.com"
    fill_in "Password", with: "password"
    click_button t("labels.sign_up")
+     click_link t("labels.sign_in")
+
+     fill_in "Email", with: "clarence@example.com"
+     fill_in "Password", with: "password"
+     click_button t("labels.sign_in")
+
+     expect(page).to have_content t("flashes.confirm_your_email")
+
+     open_email "clarence@example.com"
+     click_first_link_in_email
    expect(page).to have_content "clarence@example.com"
    click_button t("labels.sign_out")
    expect(current_path).to eq(sign_in_path)
  end
end
Because we use email spec helpers, we add the dependency to our application:
# Gemfile
gem "email_spec"
# spec/rails_helper.rb
require "clearance/rspec"
RSpec.configure do |config|
  config.include EmailSpec::Helpers
  config.include EmailSpec::Matchers
  # ...
end
The test fails because as of now the application signs the user in right after sign up, without any confirmation step. This spec will fail until we finish implementing the feature, which we now continue developing doing TDD.
New user attributes
We need to add functionality in between a user signing up and signing in. To do
so, we need new attributes in the User model to decide whether a user’s email
address is confirmed.
We add an email_confirmed_at datetime attribute to User which will indicate a
user is confirmed when it has a value. We also add an email_confirmation_token
attribute, which is the unique token that is generated on user sign up and sent
to their email, so we can validate that they have access to the email they are
signing up with.
The migration looks like this:
class AddConfirmedAtToUsers < ActiveRecord::Migration
  def change
    add_column :users, :email_confirmation_token, :string, null: false, default: ""
    add_column :users, :email_confirmed_at, :datetime
  end
end
Having null: false, default: "" options for email_confirmation_token
guarantees that the value will always be a string, whether it’s an actual token
or an empty string, avoiding unnecessary nils in our system and making sure it
always responds to String methods. This avoids type-check conditionals.
On the other hand, email_confirmed_at can be nil, and in that case the user
has not yet confirmed their email account.
Populate new attributes
Clearance sign up creates a new user by default, with no email confirmation logic. We want our application to prevent users from authenticating after a successful sign up. For this to happen, we override Clearance’s user creation endpoint to set the email confirmation token on new users, and send them an email with the token.
Let’s test drive the new controller:
describe UsersController do
  describe "#create" do
    context "with valid attributes" do
      it "creates user and sends confirmation email" do
        email = "user@example.com"
        post :create, user: { email: email, password: "password" }
        expect(controller.current_user).to be_nil
        expect(last_email_confirmation_token).to be_present
        should_deliver_email(
          to: email,
          subject: t("email.subject.confirm_email"),
        )
      end
    end
  end
  def should_deliver_email(to:, subject:)
    expect(ActionMailer::Base.deliveries).not_to be_empty
    email = ActionMailer::Base.deliveries.last
    expect(email).to deliver_to(to)
    expect(email).to have_subject(subject)
  end
  def last_email_confirmation_token
    User.last.email_confirmation_token
  end
end
To make this test pass we need to define the new controller that hooks into the Clearance sign up flow, and let our application know that we will handle sign up instead of Clearance.
We include explicit Clearance routes in our application using the generator:
rails generate clearance:routes
And modify the generated routes so the sign up endpoint routes to our soon-to-be controller:
- resources :users, controller: "clearance/users", only: [:create] do
+ resources :users, only: :create do
  resource :password,
    controller: "clearance/passwords",
    only: [:create, :edit, :update]
end
With the route hooked up, we create our controller making use of
methods defined in Clearance::UsersController. (Note that our version
doesn’t have sign_in @user). This makes the unit spec pass:
class UsersController < Clearance::UsersController
  def create
    @user = user_from_params
    @user.email_confirmation_token = Clearance::Token.new
    if @user.save
      UserMailer.registration_confirmation(@user).deliver_later
      redirect_back_or url_after_create
    else
      render template: "users/new"
    end
  end
end
UserMailer specs and implementation is outside the scope of this post.
Keep users out
At this point, we run the feature spec again, and it fails at the very same step
as before this work. We are marking users as not confirmed after sign up, but
we are not adding the check during sign in and thus behavior has not yet
changed. We will make use of Clearance SignInGuard stack to override default
behavior.
SignInGuard offers fine-grained control over the process of signing in a user.
Clearance initializes an object that inherits from SignInGuard and responds to
call with a session and the current guards stack.
SignInGuard provides methods to help make writing guards simple: success,
failure, next_guard, signed_in?, and current_user.
We can think of Guards as a middleware, in which a chain of objects is run one after the other, and if they all succeed they authenticate the user. Otherwise they display an error message and show again the sign in form.
We add a new guard to our Clearance configuration:
Clearance.configure do |config|
  config.routes = false
  config.sign_in_guards = [ConfirmedUserGuard]
end
And define it:
# app/guards/confirmed_user_guard.rb
class ConfirmedUserGuard < Clearance::SignInGuard
  def call
    if user_confirmed?
      next_guard
    else
      failure I18n.t("flashes.confirm_your_email")
    end
  end
  def user_confirmed?
    signed_in? && current_user.email_confirmed_at.present?
  end
end
With this set up, our feature spec advances a few steps to fail in the following step:
expect(page).to have_content "clarence@example.com"
We are correctly disallowing signed up users to sign in, but we are not providing them with an endpoint to actually confirm their account yet. Users who sign up and don’t confirm are properly left out, but how can a user confirm their account and authenticate?
Let users in
We create a controller spec to allow a user to confirm their account, provided they have the proper confirmation token:
describe EmailConfirmationsController do
  describe "#update" do
    context "with invalid confirmation token" do
      it "raises RecordNotFound exception" do
        expect do
          get :update, token: "inexistent"
        end.to raise_exception(ActiveRecord::RecordNotFound)
      end
    end
    context "with valid confirmation token" do
      it "confirms user and signs it in" do
        user = create(
          :user,
          email_confirmation_token: "valid_token",
          email_confirmed_at: nil,
        )
        get :update, token: "valid_token"
        user.reload
        expect(user.email_confirmed_at).to be_present
        expect(controller.current_user).to eq(user)
        expect(response).to redirect_to(root_path)
        expect(flash[:notice]).to eq t("flashes.confirmed_email")
      end
    end
  end
end
Note that we define an update action, as it’s updating a resource in the
server, but define it with a GET HTTP verb. This is because the user is not
submitting a form, but clicking on a confirmation link in their email.
The route and controller that make this spec pass follow:
get "/confirm_email/:token" => "email_confirmations#update", as: "confirm_email"
class EmailConfirmationsController < ApplicationController
  def update
    user = User.find_by!(email_confirmation_token: params[:token])
    user.confirm_email
    sign_in user
    redirect_to root_path, notice: t("flashes.confirmed_email")
  end
end
While writing the controller we find that we’d like to have that
user.confirm_email method that doesn’t yet exist, so we write its spec and
implementation:
# spec/models/user_spec.rb
describe User do
  # ...
  describe "#confirm_email" do
    it "sets email_confirmed_at value" do
      user = create(
        :user,
        email_confirmation_token: "token",
        email_confirmed_at: nil,
      )
      user.confirm_email
      expect(user.email_confirmed_at).to be_present
    end
  end
  # ...
end
class User < ActiveRecord::Base
  include Clearance::User
  # ...
  def confirm_email
    self.email_confirmed_at = Time.current
    save
  end
end
Done
We test-drove adding email confirmation to a Rails application that uses Clearance, making use of Clearance Guards for extending default behavior.
At this point all specs are green, and we can deploy this to be tested in staging servers. If it passes code review and acceptance testing, we are ready to deploy this to production. Yay!
