How to Add Auth to a Rails App (Rails 8 Generator vs Devise)

16 Jun 2026 · Bank K.

A practical guide to adding authentication to a Rails app: the Rails 8 built-in generator, has_secure_password, sessions, and Devise — with code.

For years, adding auth to a Rails app meant one of two things: install Devise, or write your own with has_secure_password and hope you didn’t miss anything. Rails 8 changed that. There’s now an official authentication generator that scaffolds a secure, convention-based setup straight into your app — no gem required.

So the question in 2026 isn’t “Devise or roll my own.” It’s “the built-in generator, or Devise, or something else?” This guide walks through adding auth to a Rails app with each option, the real code, and where each one fits.

The Rails 8 Built-in Generator

Rails 8 ships an authentication generator that produces a sane email/password system out of the box:

bin/rails generate authentication
bin/rails db:migrate

That gives you a real, secure foundation:

  • A User model using has_secure_password (bcrypt under the hood, so passwords are hashed, never stored plaintext).
  • A Session model that persists sessions in the database with a unique token, plus ip_address and user_agent columns.
  • A Current model built on ActiveSupport::CurrentAttributes for accessing the logged-in user anywhere in a request.
  • A SessionsController (login/logout) and PasswordsController (password reset), with views.
  • Migrations: a users table with a uniquely indexed email_address and password_digest, and a sessions table.

The generated User model is roughly:

class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

  normalizes :email_address, with: ->(e) { e.strip.downcase }
end

And the concern it wires into ApplicationController gives you an authenticate helper plus Current.user:

class DashboardController < ApplicationController
  def show
    @user = Current.user  # the authenticated user
  end
end

# Allow a public page through the before_action:
class HomeController < ApplicationController
  allow_unauthenticated_access only: :index
end

Two things to know. First, sessions live in the database, so revoking one is a single destroy — genuinely better than a stateless cookie for kicking someone out. Second, the generator deliberately omits a registration flow. It’s focused on secure authentication, not onboarding, so you add the signup action yourself:

class RegistrationsController < ApplicationController
  allow_unauthenticated_access only: [:new, :create]

  def create
    user = User.new(user_params)
    if user.save
      start_new_session_for(user)  # helper from the generator
      redirect_to dashboard_path
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:email_address, :password, :password_confirmation)
  end
end

Best for: Most new Rails apps. You own readable code, sessions are revocable, and there’s no gem to track.

Option 2: Devise

Devise is still the most-installed auth gem in Rails, and for good reason — it bundles a lot: registration, password reset, email confirmation, account locking, “remember me,” and an ecosystem of add-ons.

# Gemfile
gem "devise"
bundle install
bin/rails generate devise:install
bin/rails generate devise User
bin/rails db:migrate

Then protecting controllers is one line:

class DashboardController < ApplicationController
  before_action :authenticate_user!

  def show
    @user = current_user
  end
end

The trade-off: Devise is convention-heavy and somewhat magic. Customizing flows means overriding controllers and learning Devise’s structure, and you’re depending on the gem keeping pace with Rails releases. For complex needs (confirmation emails, lockable accounts, omniauth providers) it saves real time. For a simple app, it can be more machinery than you need now that the built-in generator exists.

Best for: Apps that want confirmation/lockable/recoverable features without building them, or teams already fluent in Devise.

Option 3: OAuth / “Sign in with Google”

For social login, omniauth plus a provider strategy (e.g. omniauth-google-oauth2) is the standard. The flow: redirect to the provider, handle the callback, find-or-create the user, then start a session — either with the Rails 8 generator’s start_new_session_for or Devise’s omniauth integration.

class SessionsController < ApplicationController
  allow_unauthenticated_access only: :omniauth

  def omniauth
    auth = request.env["omniauth.auth"]
    user = User.find_or_create_by(email_address: auth.info.email) do |u|
      u.password = SecureRandom.hex(32)  # random; they log in via OAuth
    end
    start_new_session_for(user)
    redirect_to dashboard_path
  end
end

Always verify the OmniAuth CSRF state (the omniauth-rails_csrf_protection gem handles this). If you’re weighing OAuth against passwordless email, the magic link authentication guide lays out the trade-offs.

Which to Pick

OptionCode you ownBuilt-in extrasBest for
Rails 8 generatorHigh (it’s yours)Auth + reset, DB sessionsMost new apps
DeviseLower (gem magic)Confirmation, lockable, etc.Feature-rich auth
OmniAuthMediumSocial providers”Sign in with Google”

For a fresh Rails 8 SaaS, start with the built-in generator. Add OmniAuth when users expect social login. Reach for Devise only if you specifically want its extra modules.

The Part None of These Cover: Payments

Every option above gets you a logged-in user. None of them charge that user money. For a SaaS you still build the billing layer yourself: the stripe gem or pay, a Stripe customer tied to each User, webhook handling for subscription events, plan gating in your controllers, and a customer portal. Then you keep session state and subscription state in sync for the life of the app.

That’s the work Beag removes. Instead of bolting a Stripe integration onto your Rails auth, Beag bundles server-validated authentication and Stripe subscriptions together. After login, the user’s plan and status are available to your frontend, so gating a premium feature is a plan check rather than a billing system:

if %w[pro enterprise].include?(current_user.plan)
  # grant access
end

No webhook controllers, no customer sync, no second system to reconcile with your users table. You keep Rails for your app and skip building payments from scratch.

A Practical Plan

  1. Run the Rails 8 generator and add your own registration action.
  2. Lock down session cookies and put password reset on a real mailer.
  3. Add OmniAuth if users expect “Sign in with Google.”
  4. When it’s time to charge, don’t hand-roll auth-plus-Stripe — use an all-in-one so identity and billing stay aligned.

Wire auth up before your features. Every model gets an owner and every controller gets a guard — do it first and everything after assumes a known user.

Add auth and Stripe payments to your Rails app fast. Get started with Beag — no webhook controllers, no billing system to build.

For more guides, see how to add auth to a Next.js app or browse the Beag blog.

About the Author
Bank K.

Bank K.

Serial entrepreneur & Co-founder of Beag.io

Founder of Beag.io. Indie hacker building tools to help developers ship faster. Previously built multiple micro SaaS products.

Ready to Make Money From Your SaaS?

Turn your SaaS into cash with Beag.io. Get started now!

Start 7-day free trial →