DEV Community

Cover image for Completing the Puzzle: Adding Signup and Password Resets to Rails 8 Auth
Zil Norvilis
Zil Norvilis

Posted on • Originally published at norvilis.com

Completing the Puzzle: Adding Signup and Password Resets to Rails 8 Auth

If you’ve tried the new rails generate authentication command in Rails 8, you know it’s a breath of fresh air. It gives you a solid, secure foundation for sessions and login without the "black box" magic of Devise.

However, the generator is intentionally minimal. It gives you the "Login" and "Logout" logic, but it leaves two big holes: How do users sign up? and How do they reset their password?

Because the auth code now lives in your app/ folder, adding these features is straightforward. You don't have to learn a gem's DSL; you just write standard Rails code. Here is how to complete your authentication system in two phases.


Phase 1: The Signup Flow (Registrations)

Signing up a user is just a standard "Create" action in ActiveRecord. We just need a controller and a view.

STEP 1: The Controller

Generate a registrations controller:

rails generate controller registrations
Enter fullscreen mode Exit fullscreen mode

Update the code to create a user and immediately log them in by starting a session:

# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
  # Allow guests to see this page!
  allow_unauthenticated_access only: %i[ new create ]

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      # The 'start_new_session_for' method comes from the 
      # Authenticated concern generated by Rails 8
      start_new_session_for @user
      redirect_to root_path, notice: "Welcome! You have signed up successfully."
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:email_address, :password, :password_confirmation)
  end
end
Enter fullscreen mode Exit fullscreen mode

STEP 2: The Routes and View

Add the routes to your config/routes.rb:

get  "signup", to: "registrations#new"
post "signup", to: "registrations#create"
Enter fullscreen mode Exit fullscreen mode

In your view (app/views/registrations/new.html.erb), use a standard form. Remember to use email_address as the field name, as that is what the Rails 8 generator uses by default in the User model.


Phase 2: Password Resets (The Modern Way)

In the old days, we had to add a reset_password_token column to our database. In Rails 8, we can use the built-in Generates Token API to create time-sensitive tokens without extra database columns.

STEP 1: Update the User Model

Tell Rails that the User model can generate tokens for password resets.

# app/models/user.rb
class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

  # Generate a token that expires in 15 minutes
  generates_token_for :password_reset, expires_in: 15.minutes do
    password_salt.last(10)
  end
end
Enter fullscreen mode Exit fullscreen mode

STEP 2: The Passwords Controller

We need two main actions: create (to send the email) and update (to change the password).

rails generate controller passwords
Enter fullscreen mode Exit fullscreen mode
# app/controllers/passwords_controller.rb
class PasswordsController < ApplicationController
  allow_unauthenticated_access
  before_action :set_user_by_token, only: %i[ edit update ]

  def create
    if user = User.find_by(email_address: params[:email_address])
      # Send the email (We'll make the mailer in the next step)
      PasswordsMailer.reset(user).deliver_later
    end
    # Always redirect to the same page so hackers can't "fish" for emails
    redirect_to new_session_path, notice: "If that email exists, a reset link has been sent."
  end

  def update
    if @user.update(params.permit(:password, :password_confirmation))
      redirect_to new_session_path, notice: "Password has been reset. Please log in."
    else
      render :edit, status: :unprocessable_entity
    end
  end

  private

  def set_user_by_token
    # Find the user using the token from the URL
    @user = User.find_by_token_for!(:password_reset, params[:token])
  rescue StandardError
    redirect_to new_password_path, alert: "Invalid or expired token."
  end
end
Enter fullscreen mode Exit fullscreen mode

STEP 3: The Mailer

Generate a mailer to send the reset link:

rails generate mailer Passwords
Enter fullscreen mode Exit fullscreen mode
# app/mailers/passwords_mailer.rb
class PasswordsMailer < ApplicationMailer
  def reset(user)
    @user = user
    # Generate the token specifically for this email
    @token = user.generate_token_for(:password_reset)

    mail to: user.email_address, subject: "Reset your password"
  end
end
Enter fullscreen mode Exit fullscreen mode

In your mailer view, create the link:

<%= link_to "Reset Password", edit_password_url(token: @token) %>
Enter fullscreen mode Exit fullscreen mode

STEP 4: The Routes

resources :passwords, param: :token
Enter fullscreen mode Exit fullscreen mode

Why this is better than a Gem

  1. No Hidden Logic: If your "Password Reset" email isn't sending, you can debug it in your own mailer file. You don't have to guess what a gem is doing.
  2. Speed: You aren't loading extra middleware or heavy dependencies.
  3. Customization: Want to add an "SMS Reset" later? Or a "Security Question"? You own the controller, so you just add the code.

Summary

The Rails 8 authentication generator is a "starter kit," not a finished product. By adding a RegistrationsController and using the generates_token_for API, you get a full-featured, enterprise-grade auth system that is 100% under your control.

Stop fighting the gems. Start writing the Ruby code.

Top comments (0)