RoR Devise: Sign in with username OR email

Ruby on-RailsAuthenticationDevise

Ruby on-Rails Problem Overview


What's the best way to enable users to log in with their email address OR their username? I am using warden + devise for authentication. I think it probably won't be too hard to do it but i guess i need some advice here on where to put all the stuff that is needed. Perhaps devise already provides this feature? Like in the config/initializers/devise.rb you would write:

config.authentication_keys = [ :email, :username ]

To require both username AND email for signing in. But i really want to have only one field for both username and email and require only one of them. I'll just visualize that with some ASCII art, it should look something like this in the view:

Username or Email:
[____________________]

Password:
[____________________]

[Sign In]

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

I have found a solution for the problem. I'm not quite satisfied with it (I'd rather have a way to specify this in the initializer), but it works for now. In the user model I added the following method:

def self.find_for_database_authentication(conditions={})
  find_by(username: conditions[:email]) || find_by(email: conditions[:email])
end

As @sguha and @Chetan have pointed out, another great resource is available on the official devise wiki.

Solution 2 - Ruby on-Rails

Solution 3 - Ruby on-Rails

def self.find_for_authentication(conditions)
  conditions = ["username = ? or email = ?", conditions[authentication_keys.first], conditions[authentication_keys.first]]
  # raise StandardError, conditions.inspect
  super
end

Use their example!

Solution 4 - Ruby on-Rails

Make sure you already added username field and add username to attr_accessible. Create a login virtual attribute in Users

  1. Add login as an attr_accessor

    Virtual attribute for authenticating by either username or email

    This is in addition to a real persisted field like 'username'

    attr_accessor :login

  2. Add login to attr_accessible

    attr_accessible :login

Tell Devise to use :login in the authentication_keys

Modify config/initializers/devise.rb to have:

config.authentication_keys = [ :login ]

Overwrite Devise’s find_for_database_authentication method in Users

# Overrides the devise method find_for_authentication
# Allow users to Sign In using their username or email address
def self.find_for_authentication(conditions)
  login = conditions.delete(:login)
  where(conditions).where(["username = :value OR email = :value", { :value => login }]).first
end

Update your views Make sure you have the Devise views in your project so that you can customize them

> remove <%= f.label :email %> > remove <%= f.email_field :email %> > add <%= f.label :login %>
> add <%= f.text_field :login %>

Solution 5 - Ruby on-Rails

https://gist.github.com/867932 : One solution for everything. Sign in, forgot password, confirmation, unlock instructions.

Solution 6 - Ruby on-Rails

Platforma Tec (devise author) has posted a solution to their github wiki which uses an underlying Warden authentication strategy rather than plugging into the Controller:

https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address

(An earlier answer had a broken link, which I believe was intended to link to this resource.)

Solution 7 - Ruby on-Rails

If you are using MongoDB (with MongoId), you need to query differently:

  def self.find_for_database_authentication(conditions={})
    self.any_of({name: conditions[:email]},{email: conditions[:email]}).limit(1).first
  end

just so it will be somewhere online.

Solution 8 - Ruby on-Rails

With squeel gem you can do:

  def self.find_for_authentication(conditions={})
    self.where{(email == conditions[:email]) | (username == conditions[:email])}.first
  end

Solution 9 - Ruby on-Rails

Here's a Rails solution which refactors @padde's answer. It uses ActiveRecord's find_by to simplify the calls, ensures there's only one call based on the regex, and also supports numeric IDs if you want to allow that (useful for scripts/APIs). The regex for email is as simple as it needs to be in this context; just checking for the presence of an @ as I assume your username validtor doesn't allow @ characters.

def self.find_for_database_authentication(conditions={})
  email = conditions[:email]
  if email =~ /@/ 
    self.find_by_email(email)
  elsif email.to_s =~ /\A[0-9]+\z/
    self.find(Integer(email))
  else
    self.find_by_username(email])
  end
end

Like the wiki and @aku's answer, I'd also recommend making a new :login parameter using attr_accessible and authentication_keys instead of using :email here. (I kept it as :email in the example to show the quick fix.)

Solution 10 - Ruby on-Rails

I wrote like this and it works out. Don't know if it's "ugly fix", but if I'll come up with a a better solution I'll let you know...

 def self.authenticate(email, password)
   user = find_by_email(email) ||
     username = find_by_username(email)
   if user && user.password_hash = BCrypt::Engine.hash_secret(password, user.password_salt)
     user
   else
     nil
   end
end

Solution 11 - Ruby on-Rails

I use a quick hack for this, to avoid changing any devise specific code and use it for my specific scenario (I particularly use it for an API where mobile apps can create users on the server).

I have added a before_filter to all the devise controllers where if username is being passed, I generate an email from the username ("#{params[:user][:username]}@mycustomdomain.com") and save the user. For all other calls as well, I generate the email based on same logic. My before_filter looks like this:

def generate_email_for_username
    return if(!params[:user][:email].blank? || params[:user][:username].blank?)
    params[:user][:email] = "#{params[:user][:username]}@mycustomdomain.com"
end

I am also saving username in the users table, so I know that users with email ending in @mycustomdomain.com were created using username.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPatrick OscityView Question on Stackoverflow
Solution 1 - Ruby on-RailsPatrick OscityView Answer on Stackoverflow
Solution 2 - Ruby on-RailsChetanView Answer on Stackoverflow
Solution 3 - Ruby on-RailsMartin SeroczyńskiView Answer on Stackoverflow
Solution 4 - Ruby on-RailsakuView Answer on Stackoverflow
Solution 5 - Ruby on-RailsKulbir SainiView Answer on Stackoverflow
Solution 6 - Ruby on-RailsMike JaremaView Answer on Stackoverflow
Solution 7 - Ruby on-RailsCamelCamelCamelView Answer on Stackoverflow
Solution 8 - Ruby on-RailstarmoView Answer on Stackoverflow
Solution 9 - Ruby on-RailsmahemoffView Answer on Stackoverflow
Solution 10 - Ruby on-RailsmaverickView Answer on Stackoverflow
Solution 11 - Ruby on-Railsamit_saxenaView Answer on Stackoverflow