How to Add Username Authentication to Your Rails App with Devise: A Step-by-Step Guide

August 31, 2024

How to Add Username Authentication to Your Rails App with Devise: A Step-by-Step Guide

In today's digital landscape, offering flexible sign-up and sign-in options can significantly improve user experience and engagement on your web application. If you're using Ruby on Rails and Devise for authentication, you might want to allow users to register and log in using either their username or email address. In this article, we'll guide you through adding a username field to your Devise-powered Rails application and configuring it to accept both usernames and emails for authentication.

Why Use Username-Based Authentication?

Usernames provide a more personalized touch to user accounts and can be easier to remember than email addresses. Allowing users to sign up and sign in using either their username or email offers flexibility and caters to a broader range of user preferences.

Step 1: Add a Username Field to Your Devise Users Table

To get started, we need to add a username field to the users table in your database. If you haven't already done so, modify the Devise migration file to include the username column.

  1. Update the Devise Migration File

    Open the Devise migration file (db/migrate/[timestamp]_devise_create_users.rb) and add the following line:

    t.string :username, null: false, default: ""
    

    Also, add an index to ensure that each username is unique:

    add_index :users, :username, unique: true
    

The final code will be like this.

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      # Username
      t.string :username,           null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :username,             unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end
  1. Run the Migration

    Apply the changes to your database by running:

    rails db:migrate
    

Step 2: Configure Devise to Support Username Authentication

Next, we need to update Devise's configuration to allow sign-in using both the username and email.

  1. Modify the Devise Configuration File

    Open config/initializers/devise.rb and set the authentication key to :login, a virtual attribute we'll define next:

    Devise.setup do |config|
      config.authentication_keys = [:login]
    end
    
  2. Add the Virtual Attribute login to the User Model

    Update your User model (app/models/user.rb) to include a virtual attribute login and override the default Devise method for finding users:

    class User < ApplicationRecord
      devise :database_authenticatable, :registerable,
             :recoverable, :rememberable, :validatable
    
      # Virtual attribute for authenticating by either username or email
      attr_accessor :login
    
      validates :username, presence: true, uniqueness: { case_sensitive: false }
    
      # Override Devise's find_for_database_authentication method
      def self.find_for_database_authentication(warden_conditions)
        conditions = warden_conditions.dup
        if (login = conditions.delete(:login))
          where(conditions.to_h).where(
            ["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]
          ).first
        else
          where(conditions.to_h).first
        end
      end
    end
    

Step 3: Permit All Necessary Parameters in Devise Controller

To ensure that all the required parameters (username, email, password, etc.) are allowed during sign-up and sign-in, update the ApplicationController:

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username, :email, :password, :password_confirmation])
    devise_parameter_sanitizer.permit(:account_update, keys: [:username, :email, :password, :password_confirmation, :current_password])
    devise_parameter_sanitizer.permit(:sign_in, keys: [:login, :password, :remember_me])
  end
end

Step 4: Update Your Devise Views to Include Username and Login Fields

Finally, modify the Devise views to support the new username and login fields.

  1. Sign-in View: Open app/views/devise/sessions/new.html.erb and add the login field:

    <div class="field">
      <%= f.label :login, "Username or Email" %><br />
      <%= f.text_field :login, autofocus: true, autocomplete: "username" %>
    </div>
    
  2. Sign-up View: Make sure the sign-up view (app/views/devise/registrations/new.html.erb) includes both username and email fields.

 <div class="field">
   <%= f.label :username, class: 'block text-sm font-medium text-gray-700' %>
   <%= f.text_field :username, autofocus: true, autocomplete: "username", class: 'mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm' %>
 </div>

Step 5: Test Your Changes

Restart your Rails server and test the following:

  • Sign up: Ensure users can register with both username and email.
  • Sign in: Verify that users can log in using either username or email along with their password.

Conclusion

By following these steps, you have successfully added a username field to your Devise-powered Rails application and configured it to allow users to sign up and sign in with either their username or email. This enhancement offers flexibility and improves the overall user experience.

For more details on Devise and its capabilities, check out the Devise GitHub Repository.

Further Reading

By providing multiple authentication options, you're making your app more accessible and user-friendly — an important factor in retaining users and boosting engagement!


Happy coding! 😊💻

Follow Me