How to Use Authentication in Ruby on Rails 8
A practical, copy-paste guide to Rails 8’s built-in authentication. Covers the new generator, sessions, sign-up, login, logout, password reset, controller protection, API-only tips, and hardening.
Rails 8 ships with a built-in authentication generator that gets you from zero to a working login system in minutes. Under the hood it uses has_secure_password, authenticate_by, and database-backed sessions, so you keep full control of the code and avoid heavyweight dependencies.
What you get out of the box
Run:
bin/rails generate authentication
This scaffolds a minimal but complete setup: a User model with secure passwords, a Session model and controller for sign-in/sign-out, a PasswordsMailer and actions for password resets, plus a concern to require authentication in controllers. You then add your own sign-up flow.
Important notes:
- The generator focuses on login and password reset. It intentionally leaves sign-up to you, since every app’s registration differs.
- Sessions are persisted in a
sessionstable and tied to a signed cookie. This gives you revocation and multi-device control without third-party gems.
Step 1: Install and migrate
# add auth scaffolding
bin/rails generate authentication
# create users and sessions tables
bin/rails db:migrate
If you are upgrading an existing app, ensure bcrypt is available, mailer host is set, and URL options are configured so password-reset links work. The Rails security guide shows that the generator adds migrations for user and session tables.
Step 2: Review the generated models
User model highlights
has_secure_passwordstores a BCrypt hash inpassword_digest.- You get
passwordandpassword_confirmationvirtual attributes. authenticate_byprovides safe credential checks that resist timing-based user enumeration and should be used in the Sessions controller.
Session model highlights
- Records active sessions with metadata like user agent and IP, and links them to a browser cookie. This enables forced logouts across devices later.
Step 3: Add a simple sign-up flow
The generator does not create sign-up screens. Here is a minimal implementation.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
# Let guests access sign-up
skip_before_action :require_authentication, only: %i[new create]
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
Current.session = @user.sessions.create! # sign in after sign-up
redirect_to root_path, notice: "Welcome!"
else
render :new, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email_address, :password, :password_confirmation)
end
end
<!-- app/views/users/new.html.erb -->
<h1>Create account</h1>
<%= form_with model: @user do |f| %>
<%= f.label :email_address %>
<%= f.email_field :email_address, autofocus: true %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Sign up" %>
<% end %>
This mirrors a common recommendation: keep registration bespoke, let the generator handle sessions and resets.
Step 4: Login and logout with authenticate_by
Use the safer authenticate_by method. It computes a password digest even when the user record is missing, which removes timing side-channels.
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
skip_before_action :require_authentication, only: %i[new create]
def new; end
def create
# authenticate_by avoids timing attacks
if user = User.authenticate_by(email_address: params[:email_address], password: params[:password])
Current.session = user.sessions.create!
redirect_to root_path, notice: "Signed in"
else
flash.now[:alert] = "Invalid email or password"
render :new, status: :unprocessable_entity
end
end
def destroy
Current.session&.destroy
redirect_to root_path, notice: "Signed out"
end
end
Why authenticate_by instead of find_by(...).authenticate? It standardizes timing, so attackers cannot infer whether an email exists.
Step 5: Password resets with zero custom crypto
Rails extends has_secure_password with a built-in password reset token and finder, with a default short expiry. You get a reset flow without rolling your own signing or token tables.
# app/models/user.rb
class User < ApplicationRecord
has_secure_password
# optionally add: generates_token_for :magic_login # for custom tokens
end
# app/controllers/passwords_controller.rb (simplified)
class PasswordsController < ApplicationController
skip_before_action :require_authentication
def create
if (user = User.find_by(email_address: params[:email_address]))
PasswordsMailer.reset(user, token: user.password_reset_token).deliver_later
end
redirect_to new_session_path, notice: "If your email exists, you will receive reset instructions"
end
def edit
@user = User.find_by_password_reset_token(params[:token])
redirect_to new_session_path, alert: "Token invalid or expired" unless @user
end
def update
@user = User.find_by_password_reset_token(params[:token])
return redirect_to new_session_path, alert: "Token invalid or expired" unless @user
if @user.update(user_params) # includes password and confirmation
redirect_to new_session_path, notice: "Password changed. Please sign in"
else
render :edit, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
end
For custom purposes such as magic links or email confirmation, use generates_token_for.
Step 6: Protect controllers with a single concern
Your generated concern typically gives you:
# app/controllers/concerns/authentication.rb
module Authentication
extend ActiveSupport::Concern
included do
before_action :require_authentication
end
private
def require_authentication
redirect_to new_session_path, alert: "Please sign in" unless Current.user
end
end
Include it in ApplicationController, then selectively open up actions with skip_before_action.
Step 7: Routes you will likely have
# config/routes.rb
resource :session, only: %i[new create destroy]
resources :passwords, only: %i[new create edit update]
resources :users, only: %i[new create] # your sign-up
root "home#index"
Step 8: API-only and SPA tips
- The Rails 8 generator can be used in API-only apps. Decide between cookie-based session auth or token-style sessions stored in the
sessionstable. For mobile clients and cross-domain SPAs, bearer tokens with revocation stored insessionswork well. - For browser SPAs on the same domain, the default session cookie is simplest and secure when combined with
SameSite=Lax, HTTPS only, and short idle timeouts. See the Security Guide for broader hardening.
Testing the happy path
Rails ships authentication test helpers with the generator. If your app predates them, create helper modules that can sign in users in integration tests, exactly as the Guides describe.
Security checklist before production
- Enforce
authenticate_byeverywhere you verify credentials. - Set secure cookie flags:
secure,httponly, andsame_site. - Configure mailers host and
force_sslin production. - Add rate limits on login and password-reset endpoints.
- Rotate session records on privilege changes.
- Add background cleanup for stale sessions.
When should you still pick Devise
Devise remains a quick way to add features like confirmations, lockable, invitable, two-factor, and OmniAuth without coding them yourself. Rails 8’s generator is a starter that keeps your auth simple, understandable, and inline with modern Rails primitives. If you need lots of modules on day one, Devise can still be a better fit.
The Bottom Line
Rails 8’s authentication generator is production-ready for most applications. It gives you:
Control: You own every line of code Security: Uses industry-standard BCrypt and timing-safe auth Simplicity: No gem dependencies, no magic Flexibility: Easy to customize for your specific needs
Trade-offs:
- More initial setup than Devise
- Features like confirmable/lockable require custom code
- No built-in OAuth (use OmniAuth separately)
For SaaS applications, internal tools, and most production apps, the Rails 8 generator is the right choice. It’s secure, maintainable, and you’ll understand exactly how it works.
Need help implementing authentication in your Rails application? I’ve built secure authentication systems for SaaS platforms, e-commerce sites, and financial applications handling sensitive data. From basic login flows to advanced security features like MFA and session management, I can help you build production-ready authentication.
Let’s discuss your project: nikita.sinenko@gmail.com
Further Reading
Need help with your Rails project?
I'm Nikita Sinenko, a Senior Ruby on Rails Engineer with 15+ years of experience. Based in Dubai, working with clients worldwide on contract and consulting projects.
Let's Talk