Omaship

February 12, 2026 · 10 min read

Rails 8 Authentication in 2026: Why Built-In Auth Beats Devise for New SaaS Projects

Jeronim Morina

Jeronim Morina

Founder, Omaship

For over a decade, "add Devise to your Gemfile" was the first step of every Rails authentication tutorial. That changed with Rails 8. The framework now ships with a built-in authentication generator that produces clean, readable code you fully own. Here is why that matters for your SaaS in 2026.

Authentication is the first real feature every SaaS needs. Before a user can see a dashboard, manage a subscription, or invite a teammate, they need to log in. For years, the Rails community outsourced this to Devise -- a powerful, configurable gem that handled everything from registration to password resets to account locking. It worked. It was battle-tested. And it was the right choice.

But Devise solved a problem that Rails itself has since addressed. Rails 8 introduced bin/rails generate authentication, a generator that scaffolds session-based authentication directly into your application. No gem dependency. No DSL to learn. No configuration files to memorize. Just plain Ruby and Rails code that you can read, modify, and extend.

What the Rails 8 authentication generator creates

Running bin/rails generate authentication produces a complete authentication system. Here is exactly what it generates:

  • User model with has_secure_password and an email address field. Passwords are hashed with bcrypt. No plain-text storage, no custom hashing schemes.
  • Session model that tracks active sessions per user. Each session gets a unique token, stored as a signed cookie. Users can have multiple active sessions across devices.
  • SessionsController for login and logout. Standard create/destroy actions. No magic callbacks or hidden before_actions -- just a controller you can read top to bottom.
  • PasswordsController for password reset flow. Generates a signed, time-limited token. Sends a reset email. Lets the user set a new password. All in about 30 lines of controller code.
  • Authentication concern that provides require_authentication, resume_session, and current_user helpers. Include it in ApplicationController and you have authentication across your entire app.
  • Migrations for the users and sessions tables. Clean schema, no unnecessary columns, no Devise-specific fields you will never use.
  • Mailer for password reset emails. A simple Action Mailer class with a single method.

That is the entire authentication system. No initializer with 50 configuration options. No modules to enable or disable. No concern about which Devise strategies are loaded. Just code in your app that does exactly what it says.

Why Devise was the right choice -- until it was not

Devise deserves respect. It has been a cornerstone of the Rails ecosystem since 2009 and has powered authentication for thousands of production applications. Here is why it was necessary:

  • Rails lacked a convention for auth. Before Rails 8, the framework had no opinion on authentication. You either rolled your own (risky) or used a gem (practical). Devise filled that gap.
  • Security is hard to get right. Devise handled password hashing, session management, CSRF protection, timing-safe comparisons, and dozens of edge cases. Writing all of that from scratch was an invitation for security bugs.
  • Feature completeness. Confirmable, lockable, trackable, omniauthable, recoverable, rememberable, timeoutable -- Devise offered modules for nearly every authentication scenario.

So what changed? Rails 8 codified the patterns that the community had converged on. has_secure_password matured. authenticate_by added timing-safe lookups. The session model pattern became standard. The generator extracted the "good parts" of authentication into vanilla Rails code.

Devise's strength -- its configurability -- became its liability. Most SaaS products use 20% of Devise's features but inherit 100% of its complexity. The generated code from Rails 8 covers that 20% with zero abstraction overhead.

The honest comparison: built-in auth vs Devise vs Rodauth

Dimension Rails 8 built-in Devise Rodauth
Setup time One command, done Gem + initializer + model config Gem + plugin config + routes
Code ownership 100% in your app Hidden in gem internals Plugin DSL, some app code
Customization Edit the code directly Override controllers/views Configure plugin blocks
Gem dependency None (just bcrypt) devise + warden + 6 transitive rodauth-rails + sequel + roda
Multi-factor auth Build it (straightforward) devise-two-factor gem Built-in OTP/WebAuthn plugins
OAuth / social login Add omniauth or build it omniauthable module Built-in OAuth plugin
Account locking Build it (~20 lines) Lockable module Built-in lockout plugin
AI agent compatibility Excellent -- standard Rails Poor -- gem-specific DSL Moderate -- Sequel/Roda layer
Upgrade path Your code, your pace Gem version bumps can break Plugin updates, generally stable
Best for New SaaS projects Existing apps, complex needs Security-critical apps

Security: what the built-in generator gets right

"But Devise is battle-tested" is the most common objection. Fair concern. Here is what the Rails 8 authentication generator does for security out of the box:

  • bcrypt password hashing. The same algorithm Devise uses. Passwords are salted and hashed with a configurable cost factor. The has_secure_password macro handles this with zero configuration.
  • Timing-safe authentication. authenticate_by prevents timing attacks by always performing the bcrypt comparison, even when the user does not exist. This is the same protection Devise provides through Warden.
  • Session token rotation. Each login generates a new session token. Session fixation attacks are prevented because tokens are never reused.
  • Signed cookies. Session tokens are stored in signed, HTTP-only cookies. They cannot be read or tampered with by client-side JavaScript. Rails handles the signing and verification automatically.
  • CSRF protection. Rails includes CSRF tokens in every form by default. The authentication generator does not change or weaken this. Every login, logout, and password reset form includes CSRF protection.
  • Password reset tokens. Generated using signed_id with an expiration time. Tokens are cryptographically signed and time-limited. No plain-text tokens stored in the database.

The security surface is smaller, not weaker. Fewer modules means fewer potential misconfiguration points. Fewer gem dependencies means fewer supply chain attack vectors. The code is in your app, so you can audit every line.

Why AI coding agents prefer built-in auth

This is the factor that most authentication comparison articles miss entirely. In 2026, AI coding agents like Claude Code, Cursor, and Codex are not niche tools -- they are how many developers write code daily. And the authentication approach you choose has a direct impact on how well these agents work with your codebase.

  • Standard patterns, no DSL. The generated authentication code uses plain Active Record, standard controllers, and regular Ruby modules. AI agents trained on millions of Rails codebases understand these patterns deeply. Devise's DSL (devise :database_authenticatable, :registerable, :recoverable) is gem-specific knowledge that agents handle less reliably.
  • Code is visible. When an AI agent needs to modify authentication behavior, it can read the controller, understand the flow, and make changes. With Devise, the agent needs to know that it should override a controller, which methods to call via super, and which callbacks are involved. Many agents get this wrong.
  • Debugging is straightforward. When something breaks in authentication, the AI agent can trace the issue through your application code. With Devise, the trace goes into gem source code, Warden middleware, and strategy classes. Agents often cannot follow this path effectively.
  • No version-specific behavior. Devise's behavior changes between major versions. An AI agent might suggest a pattern that worked in Devise 4.x but breaks in 5.x. Built-in auth is just your code -- there is no version mismatch risk.

The best code for AI agents is the code they can read, understand, and modify without needing gem-specific knowledge. Rails 8 built-in auth is exactly that.

Adding OAuth and social login

The most common follow-up question: "What about Google login? GitHub login? Social auth?" The built-in generator does not include OAuth, and that is a deliberate choice. Not every SaaS needs social login on day one. But when you do, adding it is straightforward.

The approach is simple: add the omniauth gem (or a focused OAuth library) and wire it into your existing User model and SessionsController. Because you own the authentication code, there is no conflict between the OAuth flow and your existing session management. You control both sides.

  • Add an identities table. Store OAuth provider, UID, and tokens in a separate Identity model linked to your User. This keeps OAuth data separate from your core user record and supports multiple providers per user.
  • Handle the callback. After OAuth authentication, look up the identity. If it exists, log the user in by creating a session. If not, create the user and identity together, then create a session. About 20 lines of controller code.
  • Keep email/password as fallback. Some users prefer traditional login. Some enterprise customers require it. Having both OAuth and email/password is straightforward because neither is fighting the other for control of the session.

With Devise, adding OAuth means enabling the omniauthable module, configuring callbacks in a specific way, and understanding how Devise's session management interacts with OmniAuth's flow. It works, but there are more moving parts to understand and more places where things can silently break.

When you should still use Devise

Honest advice: Devise is not obsolete. There are legitimate scenarios where it remains the pragmatic choice:

  • Existing applications. If your app already uses Devise and it works, do not rip it out. Migration is effort with risk and no feature gain. Focus on shipping product features instead.
  • Complex multi-tenant authentication. If you need database-authenticatable, confirmable, lockable, timeoutable, and trackable all at once, Devise gives you these as modules. Building all of them from scratch on top of the generator is more work than using the gem.
  • Team familiarity. If your entire team knows Devise inside and out, switching to built-in auth for a new project introduces unnecessary friction. Use what your team knows.
  • Rapid prototyping with all features. If you need a complete authentication system with email confirmation, account locking, and login tracking in the next hour, Devise gets you there faster than building on top of the generator.

The key distinction: for new SaaS projects in 2026, the built-in generator is the better starting point. For existing apps or genuinely complex authentication requirements, Devise remains a solid choice.

What about Rodauth?

Rodauth deserves mention as the most security-focused authentication option in the Ruby ecosystem. It takes a fundamentally different approach: authentication is handled at the Rack middleware layer using Roda and Sequel, separate from your Rails application code.

The advantage is isolation. Authentication logic runs in its own context with its own database library. The disadvantage is complexity -- you are adding Sequel and Roda as dependencies alongside Active Record and Rails. For applications where security requirements justify this level of isolation (financial services, healthcare), Rodauth is worth evaluating. For most SaaS products, the Rails 8 generator provides sufficient security with dramatically less complexity.

Extending the generator: common additions for SaaS

The generator gives you the foundation. Here are the most common additions SaaS products need, and how much code each requires:

  • Registration form. The generator does not include sign-up views. Add a RegistrationsController with create action and a form. About 30 lines of code total.
  • Email confirmation. Add a confirmed_at column to users, send a confirmation email with a signed token on registration, and verify the token. About 40 lines.
  • Rate limiting. Add Rack::Attack with throttling rules for login attempts and password resets. Prevents brute-force attacks. About 15 lines of configuration.
  • Account locking. Add a failed_login_attempts counter and a locked_until timestamp. Increment on failure, lock after N attempts. About 20 lines.
  • Remember me. Extend the session token with a longer-lived cookie. About 10 lines.
  • Two-factor authentication. Add the rotp gem for TOTP codes. Store the secret on the user, verify during login. About 50 lines for a basic implementation.

Each of these is a focused addition that you understand completely. Compare this to enabling a Devise module: less code, but more hidden behavior you need to understand when something goes wrong.

How Omaship handles authentication

Omaship ships with the Rails 8 built-in authentication generator, already run and configured. When you create a new project, authentication is ready from the first bin/rails server:

  • User and Session models are generated and migrated. bcrypt is configured with production-appropriate cost.
  • Login, logout, and password reset work immediately. The views are styled and integrated with the application layout.
  • The authentication concern is included in the application controller. Protected routes are protected from the start.
  • Rate limiting with Rack::Attack is configured for login endpoints. Brute-force protection is on by default.
  • No Devise dependency. No gem-specific DSL to learn. No initializer to configure. The authentication code is in your app, and AI coding agents can read and modify it without special knowledge.

The philosophy is simple: authentication should be boring infrastructure, not a feature you spend days configuring. The Rails 8 generator makes it boring in the best way -- it works, it is secure, and it gets out of your way.

The bottom line

Rails 8 solved the authentication problem that Devise was created to address. For new SaaS projects in 2026, the built-in generator is the right default. It produces code you own, code your AI agents can work with, and code that is secure without being complex.

The best authentication system is one you can read in five minutes, modify in ten, and never worry about gem version conflicts breaking. That is what Rails 8 gives you.

Devise is still a great gem. Rodauth is still the most secure option. But for the typical SaaS founder who needs login, logout, password reset, and a foundation they can extend -- the answer in 2026 is the one that ships with the framework.

Start with authentication that just works

Omaship ships with Rails 8 built-in auth, Rack::Attack rate limiting, and styled views out of the box. No Devise. No configuration. Just secure, readable code you own.

Continue reading

We use analytics and session recordings to learn which parts of Omaship help and which need work. Accept all, or customize what you share.

Privacy policy