DEV Community

Adrian Valenzuela
Adrian Valenzuela

Posted on • Updated on

Custom authentication flow with Sorcery gem, RSpec, and Rails 7 - Pt. 1

Originally posted here

UPDATE: I rearranged the order of some of the code snippets to make it easier to scan and find. At the end of the article I go into a bit more detail about what is going on in the snippets, but if any of you have any questions please feel free to ask.

We're going to install Sorcery, a low-level authentication gem to help you create a custom authentication flow for your app

I'm picking up where I left off in a post where I install RSpec onto a fresh Rails 7 app. If you want to start there so we are on the same page please feel free: Install RSpec on Rails 7

git checkout -b sorcery-sign-up-feature
bundle add sorcery
bundle install
rails g sorcery:install
rails db:migrate
Enter fullscreen mode Exit fullscreen mode

Before we write any code let's just write a simple spec to serve as our guide so we know what to focus on energy on.

Spec

# spec/system/sign_up_spec.rb

require 'rails_helper'

describe "User signs up", type: :system do
  let(:email) { Faker::Internet.email }
  let(:password) { Faker::Internet.password(min_length: 8) }

  before do
    visit root_path
  end

  scenario "register user account" do
    click_on "Register"
    fill_in "Email", with: email
    fill_in "Password", with: password
    fill_in "Password confirmation", with: password
    click_button "Sign up"

    expect(page).to have_content("Dashboard")
    expect(page).to have_content(email)
    expect(page).to have_no_content("Register")
  end
end
Enter fullscreen mode Exit fullscreen mode

I'm just going to give you all the code you need to pass that test!

Routes

# config/routes.rb

Rails.application.routes.draw do
  get 'dashboard', to: 'dashboard#show'
  get 'register', to: 'user_registration#new'
  resources :user_registrations, only: :create
  root 'home#show'
end
Enter fullscreen mode Exit fullscreen mode

Controllers

# app/controllers/home_controller.rb

class HomeController < ApplicationController
  def show
  end
end
Enter fullscreen mode Exit fullscreen mode
# app/controllers/dashboard_controller.rb

class DashboardController < ApplicationController
  def show
  end
end
Enter fullscreen mode Exit fullscreen mode
# app/controllers/user_registrations_controller.rb

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

  def create
    @user = User.new(user_params)

    if @user.save
      auto_login(@user)
      redirect_to dashboard_path, notice: "You have registered successfully."
    else
      render :new
    end
  end

  private
  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end
Enter fullscreen mode Exit fullscreen mode

Views

# app/views/home/show.html.erb

<h1>Homepage</h1>
Enter fullscreen mode Exit fullscreen mode
# app/views/dashboard/show.html.erb

<h1>Dashboard</h1>
Enter fullscreen mode Exit fullscreen mode
# app/views/shared/_navbar.html.erb

<% if current_user %>
  <%= current_user.email %>
<% else %>
  <%= link_to "Register", register_path %>
<% end %>
Enter fullscreen mode Exit fullscreen mode
# app/views/layout/application.html.erb

<%= render "shared/navbar" %>

<% if notice %><%= notice %><% end %>
<% if alert %><%= alert %><% end %>
Enter fullscreen mode Exit fullscreen mode
# app/views/user_registrations/new.html.erb

<h1>Register an account</h1>

<%= form_with(model: @user, url: user_registration_path, method: :post) do |form| %>
  <div>
    <%= form.label :email %>
    <%= form.text_field :email %>
  </div>

  <div>
    <%= form.label :password %>
    <%= form.password_field :password %>
  </div>

  <div>
    <%= form.label :password_confirmation %>
    <%= form.password_field :password_confirmation %>
  </div>

  <div>
    <%= form.submit "Sign up" %>
  </div>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Models

# app/model/user.rb

class User < ApplicationRecord
  authenticates_with_sorcery!

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: true
end
Enter fullscreen mode Exit fullscreen mode

Let's finish up

git status
git add -A
git commit -m "add sorcery sign up and auto login flow"
git checkout main
git merge sorcery-sign-up-feature
git branch -D sorcery-sign-up-feature
git push
Enter fullscreen mode Exit fullscreen mode

Continue reading for my ramblings

By the way I just wanted to point out that sweet auto_login helper method Sorcery
has to automatically log in your user. We slipped it into the create action to create
a nice seamless way to log in a user. Sorcery has the option for user activation via a
confirmation link as well if you wish to incorporate that.

Next time we will create a way to sign out and actually hide pages from users not signed in.
Right now you can see the dashboard page whether you are logged in or not but in our next
test we'll focus on that. We'll also add a way to sign out, delete users, sign in, and show form errors.
The form we made only works for registering accounts, not signing in existing ones.
If you try using the form again you'll see in the server console that it doesn't let
you move forward with the action because we set a validates uniqueness on email attributes.

Speaking of attributes, you might have noticed the migration file Sorcery generated only had 3
attributes: email, salt, and crypted_password. If you are unsure about how we were able to fill in
password and password_confirmation through the form then you might not be alone. This was done with
something called "virtual attributes". The form attributes had values but they were not persisted into
the database, the password the user wrote was encrypted and that was stored into the database. You never
want to store plain passwords in a database. No-no.

Hope you enjoyed the tutorial! This is the great thing about Sorcery,
you are in control of your authentication flow and can add or remove as much or little as you like.

Discussion (0)