May 15, 2026
Rails 8's auth generator changes my from-scratch advice
Rails 8 ships a built-in authentication generator. Here's what it actually produces, and where Devise still earns its place.
For about ten years, the answer to “how do I add login to a fresh Rails app” was “add devise.” I recommended it. I shipped it on probably twenty client apps. It works.
Rails 8 changed my default. The framework now ships with bin/rails generate authentication, which scaffolds a working email-and-password login system in one command, no gem dependency. For early-stage apps, that’s now what I reach for first. Devise is still the right answer for some apps; this post is about which, and why.
What the generator actually produces
Running bin/rails generate authentication in a fresh Rails 8 app creates roughly the following.
A User model with has_secure_password, an email field, and the necessary migration. Nothing magical here — has_secure_password has been in Rails for a decade and uses bcrypt to hash the password into a password_digest column.
A Session model, persisted to the database, with belongs_to :user, an IP address column, and a user-agent column. This is the part that’s new and worth dwelling on. Sessions aren’t cookie-only or signed-cookie-only — they’re rows in a sessions table, and the cookie holds the session’s signed ID. That means revoking a session is a database DELETE, listing a user’s active sessions is a User#sessions association, and “log out everywhere” is a user.sessions.destroy_all.
A SessionsController with new, create, and destroy actions, plus a RegistrationsController with new and create. Login, signup, logout. Bare HTML views — no styling, no JavaScript, plain <form> tags submitting to the controllers.
A PasswordsController for the password-reset flow, with token-based reset emails delivered via Action Mailer. The reset tokens are signed with MessageVerifier and time-limited (the default is 15 minutes, last I checked — verify this against your Rails version because the constant has moved between minor releases).
A Current attributes class with attribute :session, :user so controllers can write Current.user instead of threading the user through every helper.
A concern called Authentication mixed into ApplicationController, providing before_action :require_authentication and a current_user helper. Skipping the before-action on a public page is the standard skip_before_action :require_authentication, only: [...] you’d write yourself.
I haven’t audited every line of every generator template against the current Rails main, so treat this list as a fair description rather than a contract — the exact filenames and method signatures shift between point releases. The shape is stable.
What it doesn’t do
The generator deliberately stops short of the things that vary the most between apps.
No magic-link login. Despite what some early coverage suggested, the stock generator scaffolds password-based auth. There’s no token-link sign-in flow out of the box. If you want passwordless, you write it yourself or you reach for magic_link_authenticatable on top of Devise.
No OAuth. No Google, no GitHub, no “Sign in with Apple.” If you need third-party auth, you bring omniauth and wire it up — the same way you would on top of Devise’s omniauthable module.
No two-factor. No TOTP, no backup codes, no WebAuthn. There’s nothing in the generator that prevents you from adding rotp and a second step in the sessions controller, but you’re writing it.
No email confirmation flow. A user signs up, the user is logged in, the user has an unverified email. If you need a “confirm your email before you can do X” gate, that’s on you to build.
No account locking. No “5 failed attempts and the account is locked for 30 minutes.” The generator counts on you to put rate limiting at a different layer (Rack-Attack, Cloudflare, your reverse proxy) — or to add the locking logic to the sessions controller.
That list is roughly the long tail of features Devise spent a decade collecting. The generator’s pitch is: most apps don’t need any of those on day one. The 80% of new apps that need email-and-password and a reset flow can ship without a third-party gem.
The Devise comparison
The honest tradeoffs.
What the generator gives you that Devise doesn’t. Code in your repo. The sessions controller is a forty-line file you can read, modify, and grep. When you need to change behavior, you edit the controller. When something breaks, the stack trace points at code you own. That’s a meaningful change from Devise, where the same modification often involves writing a custom controller that inherits from Devise::SessionsController, navigating the routing DSL, and remembering which of the eleven Devise::* modules owns the behavior you’re changing.
What Devise gives you that the generator doesn’t. Confirmable, lockable, recoverable, trackable, timeoutable, omniauthable. These are real features, and writing them yourself takes real time. If your app is going to need email confirmation, account locking, and “remember me for 30 days” within the first six months, Devise has already solved those problems and the integration cost is bundle add devise && rails g devise:install.
What both give you that rolling-your-own doesn’t. Tested, audited code paths for the parts that matter — bcrypt cost factor, CSRF on the sessions controller, session fixation prevention on login (resetting the session after authentication), constant-time comparison on tokens. These are subtle to get right from scratch and hard to test for, and a security review will catch the absence of any of them. The generator handles the basics; Devise handles a longer tail.
How I’m choosing in 2026
For a new Rails 8 app, my decision tree:
Use the generator if the auth requirements for the next twelve months are: email/password, password reset, log out, log out everywhere. That’s most B2C SaaS, most internal tools, most prototypes. The cost is one generator command and roughly four files of code I now own. The benefit is no third-party dependency in the auth-critical path.
Use Devise if I know within six months I’ll need email confirmation and OAuth and two-factor and account locking. The Devise modules cover all of these, and the integration is a known quantity. Building the same matrix on top of the generator means writing four features from scratch, and I’d rather spend that time on the product.
Use Rodauth if I’m building a high-security app — banking, healthcare, anything where the threat model includes credential stuffing, where I need WebAuthn from day one, or where the security review process is going to scrutinize the auth layer. rodauth-rails is more involved to set up but ships features Devise either lacks or relies on plugins for. Janko Marohnić’s writeups on Rodauth are the right starting point.
Roll your own only if I have a genuinely unusual requirement — government SSO, a hardware token integration, a federation scheme — that none of the above accommodate. Even then, I lift has_secure_password and the session-as-database-row pattern from the generator and start from there.
The thing the generator quietly fixes
The session-as-row pattern is the part of the generator I want to single out. Devise stores the user ID in a signed cookie and that’s the whole session. Logging out destroys the cookie on the user’s browser; if the user has the same session cookie open on a different device, that device stays logged in until the cookie expires.
Persisting sessions to the database flips the model. The cookie holds the session ID, the database holds the session row. Logout is a database delete. “Log out everywhere” is a single SQL statement. Listing a user’s active sessions on a security page is current_user.sessions.order(updated_at: :desc). None of that is hard to add to Devise (the devise-session_limitable gem and several internal projects do it), but it’s not the default, and most Devise apps don’t have it.
The generator makes the better default the path of least resistance. That alone is reason enough to look at it for new apps, even if you end up swapping in Devise later for the modules.
What this changes for established apps
Nothing, mostly. If your app runs on Devise and works, migrating to the generator is a two-week project that delivers approximately zero user-visible value. The migration story isn’t compelling.
Where I’d think about it: if you’re already in the middle of a Rails 7 → Rails 8 upgrade and you’re planning a significant auth feature anyway (adding 2FA, restructuring sessions, building an admin login UI), it might be the moment to evaluate whether Devise still earns its place. Otherwise, the answer is “leave it alone.”
The generator is for the next app, not the current one.
The Rails 8 authentication generator is part of the framework — see rails/rails for the source. Detail-level claims about default constants (token expiry, bcrypt cost, etc.) should be verified against the Rails version you’re running; these have shifted between point releases.