Notes to self

Extending Rails authentication generator with registration flow

Rails 8 comes with a built-in authentication generator. However, it doesn’t yet come with registrations. Here’s how to add them.

Rails auth generator

To generate authentication scaffold, run:

$ rails generate authentication

This will create User and Session models as well as sign-in forms. But there is little to do without registrations.

Registrations

To add registrations, we’ll add a new RegistrationsController, route, and view.

The registration controller can look like this:

# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
  # Include Authentication module unless it's already in ApplicationController
  include Authentication
  allow_unauthenticated_access

  rate_limit to: 10,
    within: 3.minutes,
    only: :create,
    with: -> { redirect_to new_registration_url, alert: "Try again later." }

  def new
    @user = User.new
  end

  def create
    @user = User.new(safe_params)

    if @user.save
      start_new_session_for @user
      redirect_to after_authentication_url, notice: "Welcome!"
    else
      flash[:alert] = "Email or password confirmation invalid."
      render :new, status: :unprocessable_entity
    end
  end

  private

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

The code is surprisingly simple. We rely on the generator’s Authentication module and start_new_session_for method to sign people in. And we rely on has_secure_password to do the right thing when providing users with password and password_confirmation.

Note that it doesn’t come with any constrains for passwords or email address, but we can add them:

# app/models/user.rb
class User
  validates :email_address, presence: true
  validates :password, length: { minimum: 10 }, if: -> { new_record? || changes[:password_digest] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:password_digest] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:password_digest] }
end

Then let’s add the view:

<!-- app/views/registrations/new.html.erb -->
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>

<%= form_for @user, url: registrations_path do |form| %>
  <%= form.label :email_address, "Email:" %>
  <%= form.email_field :email_address, autofocus: true %>
  <%= form.label :email, "Password:" %>
  <%= form.password_field :password, required: true %>
  <%= form.label :email, "Confirmation:" %>
  <%= form.password_field :password_confirmation, required: true %>
  <%= form.submit "Register" %>
<% end %>

Finally, let’s add the route:

Rails.application.routes.draw do
  resource :session
  resources :passwords, param: :token

  # add this
  resources :registrations, except: [:index, :show, :destroy]
end

And that’s it! You can now register as a new user.

Check out my book
Interested in Ruby on Rails default testing stack? Take Minitest and fixtures for a spin with my latest book.

Get Test Driving Rails and make your tests faster and easier to maintain.

by Josef Strzibny
RSS