Rails 8 Authentication
Built in lightweight authentication for Ruby on Rails.
authentication

Lightweight and Built-in Authentication Generator in Rails 8
With the release of Rails 8, several exciting features were introduced, including Solid Queue, Kamal 2, and more. Among these additions was a new Authentication Generator. While this isn’t a full-fledged authentication system like Devise, it provides enough functionality to handle basic authentication tasks such as sessions, password authentication, and password reset emails.
Let’s dive in and explore how it works, and compare it to the elephant in the room—Devise.
The setup
First, let’s create a new Rails app and generate the authentication scaffolding. You can name the app whatever you like.
rails new app postings
cd postings
rails g authentication
rails db:migrate
After running the migrations, take a look at your schema.rb file. You’ll notice two new tables: sessions and users.
Unlike Devise, the Rails authentication generator doesn’t create a registration controller or model. This is a step you’ll need to implement yourself, as the generator is designed to be lightweight and provide just enough functionality to get you started.
Lets first add routes for new registeration process:
resource :registration, only: %i[new create] # routes for new and create
And lets create registeration controller with follow methods:
class RegisterationsController < ApplicationController allow_unauthenticated_access def new @user = User.new end def create @user = User.new(user_params) if @user.save start_new_session_for @user redirect_to root_path, notice: "Successfully registered!" else render :new end end private def user_params params.require(:user).permit(:email_address, :passworrd, :password_confirmation) end end
Here, we’re using two new methods provided by the Rails authentication module:
- start_new_session_for: creates new session for the @user
- allow_unauthenticated_access: allows user registeration.
These methods come from the Authentication module, which is located in app/controllers/concerns/authentication.rb.
Now, let’s create a view so users can register. Save this file as app/views/registrations/new.html.erb:
<div class="registeration"> <% if alert = flash[:alert] %> <p><%= alert %></p> <% end %> <% if notice = flash[:notice] %> <p><%= notice %></p> <% end %> <%= form_with model: @user, url: registeration_path, class: "registeration-form" do |form| %> <div class="form-group"> <%= form.label :email_address %> <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address], class: "form-control" %> </div> <div class="form-group"> <%= form.label :password %> <%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72, class: "form-control" %> </div> <div class="form-group"> <%= form.label :password_confirmation %> <%= form.password_field :password_confirmation, required: true, autocomplete: "current-password", placeholder: "Confirm your password", maxlength: 72, class: "form-control" %> </div> <%= form.submit "Register", class: "btn btn-primary" %> <% end %> </div>
Now if you run the server you'll see something root page of your app.
Testing
Testing the new Action
test "GET #new renders the new partial" do get new_registration_path assert_response :success assert_template partial: "registrations/_new" end test "GET #new assigns a new user" do get new_registration_path assert assigns(:user).is_a?(User) assert assigns(:user).new_record? end
Testing the create Action
setup do @valid_params = { user: { email_address: "test@example.com", password: "password123", password_confirmation: "password123" } } @invalid_params = { user: { email_address: "", password: "password123", password_confirmation: "wrongpassword" } } end test "POST #create with valid parameters creates a new user" do assert_difference("User.count", 1) do post registration_path, params: @valid_params end assert_redirected_to root_path assert_equal "Successfully registered!", flash[:notice] end test "POST #create with invalid parameters does not create a user" do assert_no_difference("User.count") do post registration_path, params: @invalid_params end assert_response :unprocessable_entity assert_template partial: "registrations/_new" assert_equal "Failed to register!", flash[:alert] end
We can run the test with following command
bin/rails test test/controllers/registrations_controller_test.rb
If everything is working and correct you'll get response like this
Running 4 tests in a single process (parallelization threshold is 50) Run options: --seed 12345 # Running: .... Finished in 0.587130s, 6.8092 runs/s, 20.4276 assertions/s. 4 runs, 12 assertions, 0 failures, 0 errors, 0 skips
That's is all for testing, we could check things like email uniqueness or password length and so on but I don't it's necessary here.
Lets take a look at authentication module
/controllers/concerns/authentication.rb
module Authentication extend ActiveSupport::Concern included do before_action :require_authentication helper_method :authenticated? end class_methods do def allow_unauthenticated_access(**options) skip_before_action :require_authentication, **options end end private def authenticated? resume_session end def require_authentication resume_session || request_authentication end def resume_session Current.session ||= find_session_by_cookie end def find_session_by_cookie Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id] end def request_authentication session[:return_to_after_authenticating] = request.url redirect_to new_session_path end def after_authentication_url session.delete(:return_to_after_authenticating) || root_url end def start_new_session_for(user) user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session| Current.session = session cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax } end end def terminate_session Current.session.destroy cookies.delete(:session_id) end end
Module actions ensures authentication by default using a before_action callback but allows exemptions with allow_unauthenticated_access. It manages sessions by storing them in the database and tracking them via signed cookies.
Key methods include resume_session to restore a user’s session, request_authentication to redirect unauthenticated users to a login page, and start_new_session_for to create a new session upon login or registration. It also supports secure features like httponly cookies and thread-safe session management via the Current class.
Quick example:
<% if authenticated? && (Current.user == @post.author) %> <div style="display: inline-flex; align-items: center;"> <%= image_tag("edit.svg", class: "svg") %> <%= link_to "Edit", edit_post_path(@post), class: "btn edit" %> <%= image_tag("delete.svg", class: "svg") %> <%= link_to "Delete", post_path(@post, category: @category), method: :delete, data: { turbo_confirm: 'Are you sure?', turbo_method: "delete" }, class: "btn delete" %> </div> <% end %>
Code above checks if user is authenticated and if current user is the author of the post.
It's simple as that just create a new user and login and you are all set.