The Rails 8 auth generator vs Devise
What bin/rails generate authentication gives you, what it leaves out, when Devise still wins, and how to move an existing app.
For fifteen years, the first line of most Rails apps’ Gemfiles after rails was devise. Rails 8 changed the default answer by shipping its own authentication generator. Now every new project starts with the same question: do we still need Devise?
Short answer: often not. Here’s how we decide.
What the generator actually gives you
bin/rails generate authentication
bin/rails db:migrate
You get real, readable code in your app — not a gem you configure:
- Models:
User(withhas_secure_passwordandhas_many :sessions),Session(storesip_addressanduser_agent), andCurrentfor the signed-in user per request. - Controllers:
SessionsControllerfor sign-in and sign-out, andPasswordsControllerfor password resets. - A concern:
Authentication, included inApplicationController. Every controller requires sign-in by default; you opt out withallow_unauthenticated_access. - A mailer:
PasswordsMailer, with HTML and text templates for the reset email. - Views: sign-in, forgot-password and reset-password forms.
- Routes:
resource :sessionandresources :passwords, param: :token. - Migrations for
users(email_address,password_digest) andsessions. - bcrypt added to your Gemfile.
Sessions are database-backed. Each sign-in creates a Session row and stores its id in a signed, permanent cookie. Signing out deletes the row. That gives you something Devise doesn’t do out of the box: a list of active sessions you can show users and revoke.
The generated SessionsController also rate-limits sign-in attempts:
class SessionsController < ApplicationController
allow_unauthenticated_access only: %i[ new create ]
rate_limit to: 10, within: 3.minutes, only: :create,
with: -> { redirect_to new_session_url, alert: "Try again later." }
def create
if user = User.authenticate_by(params.permit(:email_address, :password))
start_new_session_for user
redirect_to after_authentication_url
else
redirect_to new_session_path, alert: "Try another email address or password."
end
end
# ...
end
authenticate_by is worth noticing: it takes the same time whether or not the email exists, which protects against timing attacks that reveal who has an account.
What it deliberately leaves out
- Sign-up. There’s no registration flow. You write the controller, form and route yourself. It’s about 30 lines.
- Password rules.
has_secure_passwordchecks presence, confirmation and a 72-byte maximum. It does not enforce a minimum length or complexity — add a validation. - Email confirmation, account locking, “remember me” options, OAuth, magic links and two-factor authentication.
That isn’t a gap so much as a philosophy: you get a small, correct core and you own the rest.
Adding sign-up
Here’s the minimal version we start with:
# config/routes.rb
resource :registration, only: %i[ new create ]
# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
allow_unauthenticated_access
rate_limit to: 10, within: 3.minutes, only: :create
def new
@user = User.new
end
def create
@user = User.new(params.expect(user: [ :email_address, :password, :password_confirmation ]))
if @user.save
start_new_session_for @user
redirect_to root_path
else
render :new, status: :unprocessable_entity
end
end
end
# app/models/user.rb
class User < ApplicationRecord
has_secure_password
has_many :sessions, dependent: :destroy
normalizes :email_address, with: ->(e) { e.strip.downcase }
validates :email_address, presence: true, uniqueness: true
validates :password, length: { minimum: 12 }, allow_nil: true
end
Add a form, and you have registration. Add email confirmation the same way: a confirmed_at column, a signed token via generates_token_for, and a mailer.
When we still reach for Devise
Devise remains a good choice when you need several of these at once:
- OAuth / social sign-in through OmniAuth, already well integrated with Devise.
- Confirmable, lockable, timeoutable and trackable behavior on day one.
- Several user types (admins, customers, partners) with separate sign-in flows.
- An ecosystem gem that expects Devise, such as some admin panels or multi-tenancy libraries.
- A team that already knows Devise well and an app where auth isn’t interesting.
If your auth requirements are a checklist of Devise modules, writing all of that by hand is not a good use of your time.
When the generator wins
- New apps with email-and-password sign-in. That’s most B2B tools and internal apps.
- Teams that want to understand their auth code. Everything is in
app/; nothing hides in a gem. - Apps that want session management — “sign out other devices” is a few lines because sessions are rows.
- Apps that would otherwise fight Devise with custom controllers, overridden views and monkey-patches.
Moving an existing app from Devise
We don’t do this unless there’s a reason — fighting Devise customizations, or a planned rewrite of auth anyway. When there is, the key fact is that Devise and has_secure_password both use bcrypt, so you can keep users’ passwords.
- Generate, don’t migrate yet. Run the generator and keep its controllers, concern and views. Delete its
create_usersmigration if you already have auserstable. - Rename the columns. Devise uses
emailandencrypted_password. Either rename them toemail_addressandpassword_digestin a migration, or adjust the generated code to use your column names. Renaming is cleaner; existing bcrypt hashes keep working. - Create the
sessionstable from the generated migration. - Swap the helpers. Replace
authenticate_user!with the concern’s default behavior (andallow_unauthenticated_accesswhere needed),current_userwithCurrent.user, and Devise route helpers with the new ones. - Rebuild what you used. Confirmable, lockable or OAuth won’t come for free. List the Devise modules you actually enabled before you start.
- Ship behind a flag. Run both sign-in paths for a short period if you can, and keep Devise in the Gemfile until nobody hits its routes.
- Expect everyone to sign in again. Devise’s session cookie won’t be recognized by the new system. Tell users before you deploy.
Our rule of thumb
If the requirements fit in one sentence — “email and password, with password reset” — use the generator. If they take a paragraph, start with Devise. Either way, add rate limiting to every form that creates sessions or accounts, and enforce a sensible minimum password length.
New to the rest of Rails 8? Start with what’s new in Rails 8. If you’d rather have someone review or rebuild your auth with you, that’s what we do.
Sources
- Securing Rails Applications: Authentication — Rails Guides
- Rails authentication generator templates — rails/rails on GitHub
- Rails 8.0: No PaaS Required — rubyonrails.org
- Devise README — GitHub