Let's Build with Ruby on Rails: Extending Devise – Login With Username or Email
Tags: rails, ruby, video, tutorial
Published: 2019-08-18 16:46:32

---
title: Let's Build with Ruby on Rails: Extending Devise – Login With Username or Email
published: true
tags: rails, ruby, video, tutorial
canonical_url: https://web-crunch.com/devise-login-with-username-email/
---

Welcome to another installment of my Let’s Build with Ruby on Rails – Extending Devise series within a series. This post will teach you how to allow a user to login with either a username or email using the Devise gem.

{% youtube qS-Jc2aJH5A %}

### Getting Started

In the video screencast, I reference my [kickoff\_tailwind](https://github.com/justalever/kickoff_tailwind) Ruby on Rails application template. You can use that as I have or start from scratch. My template sets up Devise automatically so there’s less to configure upfront. I also add a new `username` and `name` column to the `User` database table which we’ll reference in this guide. If you’re not using the template be sure to at least add a `username` column on your table.

That might look like this:

```bash
$ rails generate migration add_username_to_users username:string

```

This command generates a new migration responsible for adding a `username` column to the `users` database table.

### Creating a new `:login` attribute inside the `User` model

We need a way to denote if the new login id is either a username or email. Devise ships using **only** `email` as a valid default for creating a new session (logging in) per user. To extend this we need to tweak a bit of the inner-workings of Devise to accommodate. The user model becomes the following:

```ruby
# app/models/user.rb

class User < ApplicationRecord
  attr_accessor :login

  # "getter"
  # def login
  # @login
  # end

  # "setter"
  # def login=(str)
  # @login = str
  # end

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  def self.find_for_database_authentication warden_condition
    conditions = warden_condition.dup
    login = conditions.delete(:login)
    where(conditions).where(
      ["lower(username) = :value OR lower(email) = :value",
      { value: login.strip.downcase}]).first
  end
end

```

I created a new `attr_accessor` called `:login`. This is a shorthand for the comments you see following that line of code above. It essentially creates a `getter` and a `setter` for you so you can keep your models cleaner. This is done so often that the shorthand was a great addition to ruby.

At the bottom of the class, you’ll find a new method `self.find_for_database_authentication` which extends Devise to query via warden. This uses some SQL to query for either the `username` or `email` fields given one or the other is supplied during form submission.

### Permitting new fields

With the brains of the logic hashed out we can head to the controller layer to allow the `:login` attributes to submit securely.

```ruby
# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

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

```

Above I amend my `application_controller.rb` file to include newly configured parameters. This is essentially white-listing those fields so Rails knows to accept those into the session/database. I’ve added the `:login` attribute to the `:sign_in` permission respectively.

### Updating the view layer

With those core changes in place above we can extend our view layer to include them.

```erb
<!-- app/views/devise/sessions/new.html.erb-->
<h2 class="heading text-4xl font-bold pt-4 mb-8">Log in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

  <div class="mb-6">
    <%= f.label :login, class:"label" %>
    <%= f.text_field :login, autofocus: true, class: "input" %>
  </div>

  <!-- additional fields -->
<% end %>

```

If you haven’t already added the `username` fields to your Sign up views you can do that as well.

```erb
<!-- app/views/devise/registrations/edit.html.erb-->

<h2 class="heading text-4xl font-bold pt-4 mb-8">Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>

  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="mb-6">
    <%= f.label :username, class:"label" %>
    <%= f.text_field :username, autofocus: true, class:"input" %>
  </div>

<!-- additional fields -->
<% end %>

```

* * *

```erb
<!-- app/views/devise/registrations/new.html.erb-->

<h2 class="heading text-4xl font-bold pt-4 mb-8">Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="mb-6">
    <%= f.label :username, class:"label" %>
    <%= f.text_field :username, autofocus: true, class:"input" %>
  </div>

<!-- additional fields -->
<% end %>

```

And then finally I updated my application layout to accommodate the logged in and logged out states so it’s easier to log out.

```erb
<!DOCTYPE html>
<html>
  <head>
    <title>Kickoff</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

  </head>

 <body class="bg-white">

  <% flash.each do |type, message| %>
    <% if type == "alert" %>
      <div class="bg-red-500">
        <div class="container px-2 mx-auto py-4 text-white text-center font-medium font-sans"><%= message %></div>
      </div>
    <% end %>
    <% if type == "notice" %>
      <div class="bg-green-500">
        <div class="container px-2 mx-auto py-4 text-white text-center font-medium font-sans"><%= message %></div>
      </div>
    <% end %>
  <% end %>

    <header class="mb-4">
      <nav class="flex items-center justify-between flex-wrap bg-gray-100 py-3 lg:px-10 px-3 text-gray-700 border-b border-gray-400">
        <div class="flex items-center flex-no-shrink mr-6">
          <%= link_to "Kickoff", root_path, class:"link text-xl tracking-tight font-semibold" %>
        </div>
        <div class="block lg:hidden">
          <button class="flex items-center px-3 py-2 border rounded text-grey border-gray-500 hover:text-gray-600 hover:border-gray-600">
            <svg class="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/></svg>
          </button>
        </div>
        <div class="w-full block lg:flex-1 lg:flex items-center text-center lg:text-left">
          <div class="lg:flex-grow">
            <%= link_to "Basic Link", "#", class: "block mt-4 lg:inline-block lg:mt-0 lg:mr-4 mb-2 lg:mb-0 link" %>
          </div>

          <div class="w-full block lg:flex lg:flex-row lg:flex-1 mt-2 lg:mt-0 text-center lg:text-left lg:justify-end items-center">
            <% if user_signed_in? %>
              <p class="lg:mr-2 px-4">Welcome, <%= current_user.username %></p>
              <%= link_to "Log out", destroy_user_session_path, method: :delete, class:"btn btn-default mb-2 lg:mr-2 lg:mb-0 block" %>
            <% else %>
              <%= link_to "Login", new_user_session_path, class:"btn btn-default mb-2 lg:mr-2 lg:mb-0 block" %>
              <%= link_to "Sign Up", new_user_registration_path, class:"btn btn-default block" %>
            <% end %>
          </div>
        </div>
      </nav>
    </header>

    <main class="lg:px-10 px-4">
      <%= content_for?(:content) ? yield(:content) : yield %>
    </main>
  </body>
</html>

```

### Closing thoughts

Hopefully, this proved to be useful! I believe this will be a way forward for me in my own apps. People are forgetful and sometimes remembering their username or email can prove to be a challenge. One issue I didn’t account for here is if a user forgot their username. That’s often a pattern you see out in the wild. Devise (I don’t think) supports this type of pattern by default but It seems to mimic the “Forgot password” approach. Perhaps I’ll dig into how to resolve this in a future installment.

If you have feedback, questions, or anything else please let me know in the comments. Thanks for following along!

[![Buy Me A Coffee](https://i2.wp.com/bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/black_img.png?ssl=1)](https://www.buymeacoffee.com/webcrunch)

### The Series So Far

- [Let’s Build: With Ruby on Rails – Extending Devise Series – Adding Custom Fields](https://web-crunch.com/extending-devise-custom-fields)
- [Let’s Build: With Ruby on Rails – Extending Devise Series – Confirmation Emails](https://dev.to/justalever/let-s-build-with-ruby-on-rails-extending-devise-series-confirmation-emails-egf)
- [Let’s Build: With Ruby on Rails – Extending Devise Series – Custom Routing](lets-build-with-ruby-on-rails-extending-devise-custom-routing)

### Shameless plug time

[![](https://i1.wp.com/web-crunch.com/wp-content/uploads/2019/01/YouTube-Promo3.jpg?resize=3840%2C2160&ssl=1)](http://hellorails.io)

I have a new course called [Hello Rails](https://hellorails.io). Hello Rails is a modern course designed to help you start using and understanding Ruby on Rails fast. If you’re a novice when it comes to Ruby or Ruby on Rails I invite you to check out the site. The course will be much like these builds but a super more in-depth version with more realistic goals and deliverables. [Grab your copy](https://hellorails.io/)!

Follow [@hello\_rails](https://twitter.com/hello_rails) and [myself @justalever](https://twitter.com/justalever) on Twitter.

The post [Extending Devise – Login With Username or Email](https://web-crunch.com/devise-login-with-username-email/) appeared first on [Web-Crunch](https://web-crunch.com).