If we publish a blogging platform to the internet with no security, bad people will post spam and mess it up for everyone. Let’s add a sign-in form to keep the bad guys out.
I like the Sorcery gem because it just quietly does its thing without imposing its opinions on registration flow like Devise does.
From the Sorcery readme:
// Gemfile gem 'sorcery'
bundle install
rails generate sorcery:install
rails db:migrate
That will have created a User model for us.
class User < ApplicationRecord authenticates_with_sorcery! end
We’ll need a SessionsController for the sign in method.
rails g controller Sessions
I think it’s interesting to show the tests for this. It can be a mini-tutorial for folks who have never used Sorcery before and need help writing integration tests with it.
require "test_helper"
class SessionsControllerTest < ActionDispatch::IntegrationTest
setup do
@sally = User.create! email: 'sally@example.com',
password: 'letmein'
end
test 'sign in renders a form' do
get sign_in_url
assert_response :success
assert_select 'form' do
assert_select '#credentials_email'
assert_select '#credentials_password'
assert_select 'input.submit'
end
end
test 'redirect to home page after sign in' do
post sessions_url, params: {
credentials: {
email: 'sally@example.com',
password: 'letmein',
}
}
assert_redirected_to root_url
end
test 'rerender the form if sign in fails' do
post sessions_url, params: {
credentials: {
email: 'sally@wrong.com',
password: 'wrong',
}
}
assert_response :success
assert_select '.form' do
assert_select '.error', 'Invalid Login'
assert_select '#credentials_email' do |elements|
assert_equal 'sally@wrong.com', elements[0][:value]
end
end
end
end
// routes.rb resources :sessions get :sign_in, to: 'sessions#new', as: :sign_in
class SessionsController < ApplicationController
def new
end
def create
credentials = credential_params
login(credentials[:email], credentials[:password]) do |user, failure|
if failure
@email = credentials[:email]
@error = failure
render action: 'new'
else
redirect_back_or_to(:root)
end
end
end
private
def credential_params
params.
require(:credentials).
permit(:email, :password)
end
end
The view is pretty simple but it took me a while to remember how to write ERB. I usually use HAML as it’s so much easier to read and write.
-# views/sessions/new.html.erb
<div class='session-page'>
<div class='session-panel'>
<h1>Sign in</h1>
<%= form_for :credentials, url: {action: :create}, html: {class: 'form'} do |form| %>
<div class='error'>
<%= t @error %>
</div>
<div class='input email'>
<%= form.label :email %>
<%= form.email_field :email,
value: @email,
required: true,
autofocus: true,
placeholder: 'Enter your email address'
%>
</div>
<div class='input password'>
<%= form.label :password %>
<%= form.password_field :password,
required: true,
placeholder: 'Enter your password' %>
</div>
<div class='actions'>
<%= form.submit 'Sign in', class: 'button submit primary' %>
</div>
<% end %>
</div>
</div>
That test passes and a little bit of CSS magic gives me this

I’d like the sign in to be in React someday but it’ll be a distraction for now and I’ll come back to it later. No one said that all my UI must be in React anyway.
If I create a user in the Rails console, I’ll be able to sign in.
User.create! email: 'sally@example.com', password: 'letmein'
Now we can protect the important controller actions. I find it safest to require login on everything and to then make exceptions for the public-facing actions.
class ApplicationController < ActionController::Base before_action :require_login class PostsController < ApplicationController skip_before_action :require_login, only: [:index, :show] class SessionsController < ApplicationController skip_before_action :require_login
If I run all the Rails tests now, I see a failure.
PostsControllerTest#test_create_a_blog_post:
Expected response to be a <2XX: success>,
but was a <302: Found> redirect to <http://www.example.com/>
Response body: <html><body>You are being <a href="http://www.example.com/">redirected</a>.</body></html>
I can update the test to sign in first.
test 'create a blog post requires a sign in' do
post posts_url(format: :json), params: {
post: {
title: 'A new day',
body: 'First post!'
}
}
assert_redirected_to root_url
end
test 'create a blog post' do
sign_in @sally
post posts_url(format: :json), params: {
post: {
title: 'A new day',
body: 'First post!'
}
}
assert_response :success
# ...
end
The sign_in method exists in a test helper:
module TestHelpers
module AuthenticationTestHelper
def sign_in user, options={}
password = options[:password] || 'letmein'
post sessions_url,
params: {
credentials: {
email: user.email,
password: password
},
}
end
end
end
It’s a bit of faffing to set up a test helper that is accessible to all the tests, but it make them more readable if you do.
# test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require_relative "../config/environment"
require "rails/test_help"
Dir['test/test_helpers/*.rb'].each {|file| require file.sub(/^test\//, '') }
class ActiveSupport::TestCase
parallelize(workers: :number_of_processors)
fixtures :all
end
class ActionDispatch::IntegrationTest
include TestHelpers::JsonTestHelper
include TestHelpers::AuthenticationTestHelper
end
While I am here, I’ll create a test helper for JSON testing too. That’s been bothering me for a while. I just updated the test above to use it.
module TestHelpers
module JsonTestHelper
def json_response options={}
assert_response options[:expected_response] || :success
JSON.parse response.body, symbolize_names: true
end
end
end
The last thing left to do is to hide the post Editor if the user is not signed in. There are a bunch of ways we can send the current user to the React client. The easiest, for now, is to add a data property to the div that contains the React application. We can do something more sophisticated later.
<div id='react' data-user-id='<%= current_user&.id%>' />
We can read this property from Javascript and store it somewhere where it is accessible from any component.
I’m sure this will get more complex later but let’s keep it simple for now.
// users/current_user.js
const current_user = {
id: null,
}
export function signIn(id=true) {
current_user.id = id
}
export function signOut() {
current_user.id = null
}
export function signedIn() {
return current_user.id
}
We can read the ID and store it when the app loads.
// application/index.jsx
document.addEventListener('DOMContentLoaded', () => {
const react = document.querySelector('#react')
signIn(react.getAttribute('data-user-id'))
// ...
Now we can call the signedIn() method from the editor and render nothing if the user is not signed in.
if(!signedIn()) return null
We can also call signIn() and signOut() in our tests.
