Using Clearance with Rails 7

For some time now, the Ruby on Rails community has anointed Devise as its preferred authentication solution. I’ve repeatedly used Devise, and it’s great. Not all of us have the time or expertise to roll our own auth, so it’s fantastic a solution exists that is widely adopted. It was only natural that I’d reach for Devise again as I started a new Rails 7 website, right?

I did, at first.

Ruby on Rails 7 was released in December of 2021. Hotwire was a big part of that release, which will require some retrofitting by gem maintainers and folks running their own apps. With this in mind, I peeked in on Devise’s Github issues to see how things were coming along with Rails 7, and, at the time, compatibility looked a little patchy.

Quick aside: Let’s all be cordial with our hard-working OSS maintainers. We all get to leverage their hard work in building our projects every day. Please give them a little space to work, and don’t shout at them when they’re not moving at the same pace as you.

I was working through the comments to see how big of a lift it would be to get Devise going on Rails 7 when I saw someone mention the Clearance gem.

Clearance is intended to be small, simple, and well-tested. It has opinionated defaults but is intended to be easy to override.

Lovely! I’m the only one who will be using the app I’m building, so small and simple sounds excellent. And guess what? The awesome thoughtbot folks maintain Clearance, so I feel good about what I’m getting out of the box. Great, let’s do this!

If you’re magically convinced and want to head over to Github to rock and roll, the Clearance gem is here.

Installation

Starting from scratch, let’s kick off a new Rails 7 app:

rails new myapp -j esbuild -c bootstrap

Those two flags state that I want to use esbuild to handle all my JavaScript and Bootstrap to provide me with a CSS framework.

Next, I’m going to generate a controller so I can have a homepage to set the root route to.

rails g controller home

Define your route:

# config/routes.rb

# ...
root "home#show"
# ...

Let’s create our homepage next.

# app/views/home/show.html.erb
<h1>Clearance Gem Demo</h1>

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def show; end
end

We now have the most amazing Rails app ever, but let’s keep going and install Clearance. Add the gem to your Gemfile.

# Gemfile
#...
# Authentication
gem "clearance", "~> 2.5"
#...

Run bundle install to install the gem.

Now, run the Clearance generator to get the basics installed:

rails g clearance:install

You will receive some additional instructions upon installation. Go ahead and follow them while considering the second step. People can easily access your login form if you add sign-in links to publicly accessible pages. More on that later.

Configuring

There are three changes you may want to make to config/initializers/clearance.rb:

# config/initializers/clearance.rb
Clearance.configure do |config|
  config.routes = false # We want to set our own
  config.redirect_url = "/dashboard" # This will be a restricted section
  config.allow_sign_up = false # Don't allow random people to sign up.
end

I typically like to change the default routes because naughty people can’t attack what they can’t find. Aptly named routes like “admin”, “sign-in”, “login”, etc., are logical guesses for someone trying to gain access to your login forms. Given that, have fun with some names. Get creative!

Run the following generator to inject the Clearance routes into your config/routes.rb.

rails generate clearance:routes

You should now see something similar to this in config/routes.rb:

resources :passwords, controller: "clearance/passwords", only: [:create, :new]
resource :session, controller: "clearance/sessions", only: [:create]

resources :users, controller: "clearance/users", only: [:create] do
  resource :password,
  controller: "clearance/passwords",
  only: [:edit, :update]
end

get "/sign_in" => "clearance/sessions#new", as: "sign_in"
delete "/sign_out" => "clearance/sessions#destroy", as: "sign_out"
get "/sign_up" => "clearance/users#new", as: "sign_up"

Feel free to change these as you see fit. E.g. change the sign in path to get "tacos-are-delicious" => "clearance/sessions#new", as: "sign_in". The idea being to obfuscate the default Clearance paths, which are publically known. You make the call on what’s appropriate for your app’s users.

Restricting Access to Sections

Clearance lets you restrict specific routes (I.e., an admin section) with a constraint, so those sections of your app will 404 on folks who aren’t signed in. Let’s make a section that is walled off to non-users.

rails g controller dashboard

Open config/routes.rb and define the routes within a constraint.

# config/routes.rb
constraints Clearance::Constraints::SignedIn.new do
  get "/dashboard", to: "dashboard#show"
end

The path /dashboard is now only available to logged-in users. You can see how easy it is to drop all your “admin” routes inside this constraint to get it protected from public access. Again, an excellent solution for many situations.

Finish this little demo up by adding a view and controller action:

# app.controllers/dashboard_controller.rb
class DashboardController < ApplicationController
  def show; end
end

# app/views/dashboard/show.html.erb
<h1>Dashboard</h1>
<p>Only logged-in users can see this.</p>

The Rails 7 Tweaks

Hopefully, you have the gist of things now. There are a few things left to take care of before we’re truly up and running. There are a couple of forms that aren’t quite ready for Turbo. For example, if you tried to reset your password and submitted a blank form, you’d get a console error stating, “Error: Form responses must redirect to another location.” You’d also see no flash error messages. That’s less than ideal.

We can’t manipulate the Clearance controller actions, but we can tweak the views.

rails generate clearance:views

You can now open up the various forms and opt out of Turbo with data: {turbo: false}. Ideally, this is a temporary solution while the thoughtbot folks update the gem for Rails 7. To be clear, we are losing the Turbo goodness by doing this. Otherwise, we have a working solution.

Here’s a form tweak example:

# app/views/passwords/new.html.erb

# original
<%= form_for :password, url: passwords_path do |form| %>

# opting out of Turbo
<%= form_for :password, url: passwords_path, data: {turbo: false} do |form| %>

Conclusion

I’m very pleased with the Clearance gem and want to thank @thoughtbot and all contributors. I’m sure the gem will receive its final polish for Rails 7, and the installation will be even smoother in the future. If you need a solid login solution that’s easy to get up and running, I have no problem recommending this gem.

Written by Matt Haliski
Consumer of tacos