Back to all blogs

Built-in Authentication in Rails 8.0 – A Technical Deep Dive and Comparison

11 min read

Rails 8.0 introduces a native authentication system built right into the framework. This new system—added via a generator—provides the fundamental pieces for user login, session management, and password recovery without relying on external gems. In this article, we'll explore Rails 8.0's authentication in depth, compare it with popular gems like Devise and Authlogic, examine its security features and best practices, discuss potential drawbacks, and walk through an implementation guide with code examples.

Technical Deep Dive into Rails 8.0's Authentication System

Rails 8.0 includes an authentication generator that scaffolds a basic email/password auth setup. The goal is to leverage Rails' own features (such as has_secure_password, encrypted cookies, etc.) to handle authentication in a transparent way. Let's break down what the generator provides and how it works under the hood.

Components and Setup

When you run the generator with:

bin/rails generate authentication

Rails creates several files that form the basis of the authentication system:

  • User Model and Migration
    The generator creates a User model (and a users table) that uses has_secure_password for secure password storage. This adds a password_digest attribute (populated using BCrypt) and a unique email field (often called email_address).

    class User < ApplicationRecord
      has_secure_password       # adds password hashing and authentication
      has_many :sessions, dependent: :destroy
      # Additional validations and customizations can be added here.
    end
    
  • Session Model and Migration
    A Session model (with a corresponding sessions table) is generated to track login sessions. Each session record stores a unique token (generated using has_secure_token), along with metadata such as the user's IP address and user agent.

    create_table :sessions do |t|
      t.references :user, null: false, foreign_key: true
      t.string :token, null: false
      t.string :ip_address
      t.string :user_agent
      t.timestamps
    end
    add_index :sessions, :token, unique: true
    
  • Current Attributes
    A Current model is created using ActiveSupport::CurrentAttributes to hold request-specific attributes (like the currently logged-in user). This allows you to reference Current.user throughout your application during a request.

  • Controllers and Views
    The generator creates a SessionsController (for login/logout) and a PasswordsController (for password resets), along with the corresponding views (ERB templates) for forms and actions. A mailer (e.g., PasswordMailer) is also generated to send password reset emails.

  • Authentication Concern
    Core authentication logic is packaged in an Authentication module (a controller concern) that is included in ApplicationController. This concern provides methods to check login status, manage sessions, and resume sessions from cookies.

After generating the files, run:

bin/rails db:migrate

to create the necessary database tables.

Authentication and Session Management Flow

Rails 8's native authentication uses a persistent session approach:

  • Logging In (SessionsController#create)
    The login action finds and authenticates the user using a secure method (such as User.authenticate_by), which verifies the email and password using BCrypt. On success, a new Session record is created and a signed, permanent cookie is issued.

    # app/controllers/sessions_controller.rb
    def create
      if user = User.authenticate_by(params.permit(:email_address, :password))
        start_new_session_for(user)  # creates Session record and sets cookie
        redirect_to after_authentication_url
      else
        redirect_to new_session_url, alert: "Try another email address or password."
      end
    end
    
  • Session Creation and Cookie Storage
    The helper method start_new_session_for(user) creates a session record (including metadata like IP and user agent) and sets a secure, signed cookie containing the session's token. This cookie (with HTTP-only and Secure flags) is used to resume sessions on subsequent requests.

  • Resuming Sessions
    The Authentication concern's require_authentication filter (used in ApplicationController) checks for a valid session cookie on each request. If found, it loads the corresponding Session record and sets Current.user. If not, it redirects the user to the login page.

  • Logging Out (SessionsController#destroy)
    Logging out involves deleting the Session record and removing the cookie, ensuring that the session token cannot be reused.

  • Login Throttling
    The generated SessionsController includes a simple rate limit to prevent brute-force attacks—limiting the number of login attempts (e.g., 10 requests per 3 minutes per IP).

Password Reset Functionality

Rails 8's built-in system also provides a password reset feature:

  • Requesting a Password Reset
    Users click a "Forgot password?" link that takes them to /passwords/new (handled by PasswordsController#new). Submitting the email triggers PasswordsController#create, which sends a reset email using a token.

  • Token Generation and Email
    Instead of storing the reset token in the database, Rails uses a signed, time-limited token generated through has_secure_password. The token is embedded in a link in the email (e.g., /passwords/:token/edit).

  • Resetting the Password
    When the user clicks the link, PasswordsController#edit verifies the token. If valid, the reset form is shown. The update action then updates the user's password (which is hashed via BCrypt). The token is single-use and expires after a set period (by default, 15 minutes).

Comparison with Third-Party Authentication Gems

Rails 8 Native Auth vs. Devise

  • Features & Flexibility:

    • Devise is feature-rich, offering modules for email confirmation, account locking, tracking sign-ins, and more.
    • The Rails 8 built-in system is intentionally minimal, covering basic email/password login and password resets. Advanced features must be implemented manually.
  • Ease of Use:

    • Devise provides ready-to-use generators, views, and a wealth of community documentation.
    • The native system offers full transparency since all code is part of your app, allowing for easier customization, though it requires extra work (e.g., implementing registration).
  • Security:
    Both solutions use BCrypt for password hashing, signed cookies, and secure token generation. Rails 8's native approach also offers a dedicated Session model, giving explicit control over session management.

  • Community & Support:
    Devise is battle-tested and has extensive community support. However, since the native auth is now part of Rails core, it will receive official documentation and maintenance, making it an attractive option for new apps.

Rails 8 Native Auth vs. Authlogic and Others

  • Authlogic provides a similar, ORM-based solution, but its community is smaller.
  • Clearance or Sorcery offer lightweight alternatives that can be compared with Rails 8's approach.
  • The native system is similar to these minimal solutions but benefits from being integrated directly into Rails, ensuring tighter coupling with Rails' security and configuration defaults.

Security Considerations and Best Practices

Rails 8's built-in authentication system follows best practices, but you must also follow some guidelines:

  • Password Storage:
    Use has_secure_password to ensure passwords are stored as BCrypt hashes. Never store plain-text passwords.

  • Password Complexity:
    While Rails enforces a minimum length (default 6 characters), you may want to add additional complexity requirements or use gems like pwned to check against breached passwords.

  • Multi-Factor Authentication (MFA):
    The built-in system does not include MFA by default. If needed, integrate an MFA solution using gems like rotp or custom logic.

  • Session Management:
    The system uses a dedicated Session model with signed cookies. Always use HTTPS in production and consider implementing session expiration based on inactivity.

  • CSRF and XSS Protection:
    Rails' default CSRF protection is enabled in controllers, and its view helpers escape output by default. Ensure you do not disable these protections.

  • Rate Limiting:
    Use the provided login throttling to mitigate brute-force attacks, and consider expanding this to include account lockout after multiple failed attempts.

  • Email Security:
    Ensure password reset emails are sent securely and that tokens are time-limited to prevent reuse.

  • Audit Logging:
    Log security-relevant events (login, logout, password resets) to detect suspicious activity.

Potential Drawbacks and Limitations

While the Rails 8 native auth system is a robust starting point, it has some limitations:

  • No Out-of-the-Box Registration:
    The generator does not include user sign-up functionality, so you must implement your own registration flow.

  • Lack of Email Confirmation:
    There is no built-in email verification. If required, you'll need to add a confirmation process manually.

  • No Advanced Security Features:
    Features such as account lockout, CAPTCHA integration, and MFA are not included by default.

  • Minimal User Model:
    The generated User model is very basic. Additional fields (e.g., for tracking sign-ins) must be added as needed.

  • No OmniAuth/Social Login Integration:
    Social login functionality (Google, Facebook, etc.) is not provided, so you'd need to integrate OmniAuth manually if desired.

  • Maturity:
    Being new, the native system hasn't been battle-tested as thoroughly as Devise. Edge cases might still emerge as you scale your app.

Implementation Guide with Code Examples

Below is a step-by-step guide to implementing the built-in authentication system in a new Rails 8.0 application.

1. Generate the Authentication Scaffold

Run the generator:

bin/rails generate authentication

This command creates:

  • Models: User (with has_secure_password) and Session
  • Controllers: SessionsController and PasswordsController
  • Mailers and views for password resets
  • An Authentication concern to be included in ApplicationController

Then run:

bin/rails db:migrate

2. Adding User Registration (Sign-Up)

Since registration isn't generated, add a simple registration flow.

Update config/routes.rb:

resources :users, only: [:new, :create]

Create app/controllers/users_controller.rb:

class UsersController < ApplicationController
  allow_unauthenticated_access only: [:new, :create]
  before_action :resume_session, only: [:new]

  def new
    if Current.user
      redirect_to root_path
    else
      @user = User.new
    end
  end

  def create
    @user = User.new(user_params)
    if @user.save
      start_new_session_for(@user)
      redirect_to root_path, notice: "Welcome, you have signed up successfully!"
    else
      flash[:alert] = @user.errors.full_messages.to_sentence
      render :new, status: :unprocessable_entity
    end
  end

  private

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

Create the view app/views/users/new.html.erb:

<h1>Create an Account</h1>
<% if flash[:alert] %>
  <div style="color:red"><%= flash[:alert] %></div>
<% end %>

<%= form_with model: @user, url: users_path do |f| %>
  <div>
    <%= f.label :email_address, "Email" %><br>
    <%= f.email_field :email_address, required: true, autofocus: true %>
  </div>
  <div>
    <%= f.label :password, "Password" %><br>
    <%= f.password_field :password, required: true %>
  </div>
  <div>
    <%= f.label :password_confirmation, "Confirm Password" %><br>
    <%= f.password_field :password_confirmation, required: true %>
  </div>
  <%= f.submit "Sign Up" %>
<% end %>

Enhance the User model (app/models/user.rb):

class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

  normalizes :email_address, with: ->(e) { e.strip.downcase }

  validates :email_address, presence: true, uniqueness: true,
            format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :password, presence: true, length: { minimum: 6 }
end

3. Logging In and Out

Use the generated login view (app/views/sessions/new.html.erb) and controller (SessionsController) to log in. For example, in your layout:

<% if authenticated? %>
  Logged in as <%= Current.user.email_address %>.
  <%= button_to "Sign Out", session_path, method: :delete %>
<% else %>
  <%= link_to "Sign In", new_session_path %> or <%= link_to "Sign Up", new_user_path %>
<% end %>

The SessionsController#create method will authenticate the user and create a session, while SessionsController#destroy logs the user out.

4. Password Reset Flow

  • Request Reset:
    The "Forgot password?" link directs users to /passwords/new, where they enter their email. The controller action sends a password reset email using a signed, time-limited token.

  • Reset Token and Email:
    The mailer generates a reset token (using Rails' built-in token generation with has_secure_password) and includes it in a link (e.g., /passwords/:token/edit).

  • Reset Form and Update:
    Clicking the link takes the user to a reset form (PasswordsController#edit) that verifies the token. The user then enters a new password; PasswordsController#update saves the new password (hashed via BCrypt).

5. Role-Based Authorization Example

To implement basic role-based authorization, add a column (e.g., admin) to the users table:

bin/rails generate migration AddAdminToUsers admin:boolean:default(false)
bin/rails db:migrate

Then, in a controller that requires admin access:

class AdminController < ApplicationController
  before_action :require_authentication
  before_action :require_admin

  def dashboard
    # Admin-specific logic here
  end

  private

  def require_admin
    redirect_to root_path, alert: "Not authorized" unless Current.user&.admin?
  end
end

In views, use Current.user.admin? to conditionally display admin links.

Conclusion

Rails 8's built-in authentication system is a significant evolution, providing a transparent and Rails-conventional approach to basic email/password authentication. It leverages secure defaults like BCrypt, signed cookies, and a dedicated Session model while leaving room for customization. Though it doesn't include all the bells and whistles of gems like Devise (e.g., email confirmation, account locking, OmniAuth integration), its minimalism means you have full control over the code and can extend it as needed.

This guide has walked through the inner workings, security best practices, comparisons with popular solutions, and step-by-step implementation examples. As Rails continues to evolve, you can expect the native authentication system to mature further—making it a viable choice for many new applications.