Devise Auth Setup in Rails 7

Integrating Devise Auth with Rails 7 has some undocumented twists. Learn about them here or use the included template to bypass them

Nick Francisci
Better Programming

--

Criss-crossing street car rails
Photo by foxfox via Unsplash

Rails 7 is here but — to my surprise — there are some twists to getting it working with Devise that I couldn’t find explained except by piecing together issues on the Devise GitHub and StackOverflow over the course of a few hours. To save you a bit of time, here’s how to do it.

Briefly, our target at the end of this article are to have a Rails 7 install with Devise’s default modules and auto-generated views working. The companion code to this article can be viewed in its entirety on GitHub. Moreover — if you are starting a fresh app — you can use the repo, which is configured as a template — to copy out as the starting point for your own app without needing to read anything further!

Generic Setup for Rails 7 and Devise

To start, I presume you already have a rails 7 application and whatever database you’re working with is set up. If you don’t, this takes no more than creating a rails app, as documented in step 3.2 of the Rails getting started guide. Assuming you have the dependencies installed (ruby, rails) all you need is to run the following in console to make a new app creatively named app_name.

rails new app_name
cd app_name

To start our own journey, first add Devise as a dependency for your app. You can either add it your Gemfile manually, or pick up the latest version automatically by running the following in your console

bundle add devise

Your Gemfile should now include something like

# Gemfile
gem "devise", "~> 4.8"

Now install it, via the console.

bundle install

Then— just like on the Devise readme — we generate the Devise config.

rails g devise:install

Working Around Rails 7’s Turbo

Now, we need to do something a little special for Rails 7. Rails 7 includes Turbo as a cornerstone component. Turbo lets you run asynchronous page updates without writing any Javascript (which is nifty) but it does it by hijacking the normal flow of submitting forms and following links. Devise isn’t (yet) prepared for that and it won’t be able to display flash messages — which it relies heavily on — by default. We need to alter the code that Devise generates for us to deal with Turbo.

So, once you’ve run rails generate devise:install we need to alter the Devise initializer config in several places beyond what the Devise README instructs us to do and add a controller as Devise’s parent controller. Credit where it’s due: these changes are from Go Rails video on the topic which also explains why these changes are necessary.

Back to Devise

As per normal Devise setup, we also need to tweak the action mailer config so that we can send password recovery emails in development.

# config/environments/development.rb, near the other action_mailer config. ~line 40 in an unaltered config file.config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

Also, as in normal Devise setup, we need to include the ability for our layout to show alert and notice flash notifications, since Devise uses them extensively to let us know the results of sign up, in, and out attempts.

# app/views/layouts/application.html.erb, above the <%= yield %><% if notice %>
<p class="alert alert-success"><%= notice %></p>
<% end %>
<% if alert %>
<p class="alert alert-danger"><%= alert %></p>
<% end %>

With the config all set up, we’re ready to generate our authenticatable model. We’ll use the standard name user but — as is well documented in the devise README, you can name it whatever you want (though some of the method and file names mentioned later here will change). In console, run

rails g devise user

Make any modifications you need to the generated db/migrate/<timestamp>_devise_create_users.rb file to add either your own columns or columns to support additional Devise modules. Here, I presume no modification is necessary. Then, run your migrations via the console

rake db:migrate

You’re now able to run your app and it should include pages like /users/sign_up and /users/sign_in. In theory Devise should work now but, unfortunately, the view links and redirects used by Devise won’t work in Rails 7. Let’s fix that.

If you navigate to /users/edit in your browser (http://localhost:3000/users/edit if you include the protocol and domain), and try to use the cancel account link it will cancel the account, but show an error when redirecting. The error will say something like undefined method `users_url’ for … RegistrationsController . It’s telling us that, after deleting the user, Devise is trying to redirect us to the user#index route which does not exist yet. Let’s route that path back to the sign in page.

# config/routes.rbRails.application.routes.draw do
devise_scope :user do
# Redirests signing out users back to sign-in
get "users", to: "devise/sessions#new"
end
devise_for :users
end

NOTE: I’ve tried to figure out how to change the url that Devise’s deletion redirects to, but so far none of the methods I’ve found online seem to work for Rails 7. If you figure it out, please let me know in the comments!

That should do it! Our out-of-the box Devise setup is now working with Rails 7. Once again, if you’d like to refer to any of the code for this setup, or use the template wholesale for a new app, the code is available on GitHub, and you may also use it as a template repo to kick off your own Rails 7 devise projects.

Final Notes: Turbo Links, Devise, and Deletes

If you later add a log-out link (or any other link) with a method: :delete, the link will not work! Turbo will once again hijack the request and turn it into a GET request instead, thus hitting the wrong controller endpoint. You can specify that the link should not use turbo (data: {turbo: false}). That will work, but it will double send the delete request. I suspect it’s related to the precept that links shouldn’t change application state. DELETEs do change application state. I suspect that’s why Turbo doesn’t support this behavior.

The easier and better alternative is to use a button to send the delete request, which works just fine.

<%= button_to(
"Log Out",
destroy_user_session_path,
method: :delete
) %>

Happy coding!

PS. Would you like to work with me selling pet drugs on the internet? Koala Health is hiring. Here’s a whole article about why you should work with us.

Resources

--

--

Engineer motivated by helping folks access the fundamentals of life: wholesome food, education, and healthcare. I also make video games. Web: nickfrancisci.com