Rails and Devise – Login using either username or email

This is essentially a no-nonsense step-by-step description of the most common case from: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address

1) add a field to the User table

rails generate migration add_username_to_users username:string:uniq

1b) check that the migration does:
2) apply the migration

rake db:migrate

3) modify app/controllers/application_controller.rb

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

  protected

  def configure_permitted_parameters
    added_attrs = [:username, :email, :password, :password_confirmation, :remember_me]
    devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
    devise_parameter_sanitizer.permit :account_update, keys: added_attrs
  end
end

4a) EITHER add “login” as an attr_accessor on the User model

# Virtual attribute for authenticating by either username or email
  # This is in addition to a real persisted field like 'username'
  attr_accessor :login

4b) OR implement the GET and SET yourself

  def login=(login)
    @login = login
  end

  def login
    @login || self.username || self.email
  end

5a) EITHER tell Devise to GLOBALLY use this “login” virtual attribute

config/initializers/devise.rb
config.authentication_keys = [ :login ]

5b) OR specify it on the User model in the “devise” statement

devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, 
         :validatable, :authentication_keys => [:login]

6) Create a method “find_for_database_authentication” in User model

    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
      elsif conditions.has_key?(:username) || conditions.has_key?(:email)
        where(conditions.to_h).first
      end
    end

NOTE! Read the original post for specific problems and/or details!!! There are some caveats!

7) Add validations on the User model

validates :username,
  :presence => true,
  :uniqueness => {
    :case_sensitive => false
  } # etc.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.