Password Reset Flow Pattern
Secure password reset using Rails 8's built-in token generation. No external gems required.
When to Use
- Building "forgot password" functionality
- Implementing email-based password recovery
- Adding secure token-based password reset
Security Principles
- Don't reveal user existence - Same message for valid/invalid emails
- Time-limited tokens - 15-minute expiry by default
- Single-use tokens - Token invalidated after password change
- Signed tokens - Cryptographically secure, tamper-proof
Quick Start
1. User Model
Rails 8's has_secure_password provides built-in support:
class User < ApplicationRecord
has_secure_password
# Automatically provides:
# - password_reset_token (generates signed token)
# - find_by_password_reset_token!(token)
end
2. Passwords Controller
# app/controllers/passwords_controller.rb
class PasswordsController < ApplicationController
allow_unauthenticated_access
before_action :set_user_by_token, only: %i[edit update]
def new
end
def create
if user = User.find_by(email_address: params[:email_address])
PasswordsMailer.reset(user).deliver_later
end
# Always show success - don't reveal if user exists
redirect_to new_session_path, notice: t("passwords.flash.create.success")
end
def edit
end
def update
if @user.update(password_params)
redirect_to new_session_path, notice: t("passwords.flash.update.success")
else
render :edit, status: :unprocessable_content
end
end
private
def password_params
params.permit(:password, :password_confirmation)
end
def set_user_by_token
@user = User.find_by_password_reset_token!(params[:token])
rescue ActiveSupport::MessageVerifier::InvalidSignature
redirect_to new_password_path, alert: t("passwords.flash.invalid_token")
end
end
3. Routes
resources :passwords, param: :token
4. Mailer
class PasswordsMailer < ApplicationMailer
def reset(user)
@user = user
mail subject: t('passwords.mailer.reset.subject'), to: user.email_address
end
end
Token Mechanics
# Generate token
user.password_reset_token
# => "eyJfcmFpbHMiOnsibWVzc2FnZSI6Ik..."
# Find user by token
User.find_by_password_reset_token!(token)
# => #<User id: 1, ...>
# Default expiry: 15 minutes
# Customize in: config/initializers/active_record.rb
Rails.application.config.active_record.password_reset_token_in = 1.hour
Reference Files
For complete implementation details:
- controllers.md - Full controller with rate limiting, session invalidation
- views.md - Request and reset forms
- mailer.md - Email templates (HTML and text)
- i18n.md - All translation keys
- testing.md - Controller, mailer, and system specs
- security.md - Rate limiting, logging, password validation