How to SignIn with an Email — Ruby on Rails and Devise

Matias Carpintini.
3 min readSep 28, 2020
Photo by Stephen Phillips — Hostreviews.co.uk on Unsplash

Passwords are a problem for users. A good practice to skip this is by sending an access link to your account by email, that link has a token that we will use to validate and finally, sign in to the user to their account. Next, I’m going to show you an implementation in Ruby On Rails 6 with Devise.

NOTE: I am going to create an application from 0 for avoid mistakes.

Setting up

Create the project: $ rails new magicLinks -T --skip-turbokinks --database=postgresql

Installing dependencies…

Add this to your Gemfile.rb

gem 'devise', '~> 4.7', '>= 4.7.1'
group :development, :test do
gem 'letter_opener', '~> 1.7' // For open emails in the browser
end

Then, $ bundle install
Let's install Devise: with $ rails g devise:install && rails g devise User && rails db:create && rails db:migrate

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_mailer.delivery_method = :letter_opener

Devise asks us for a root path, let’s do that: $ rails g controller welcome index.
Add this in your app/config/routes.rb:

root 'welcome#index'

Let’s do it 👊

First, we need to create a table for save the generated tokens on our db.
So, $ rails g model EmailLink token expires_at:datetime user:references && rails db:migrate

Now we are going to generate the token and send the email when this happens, for that, in app/models/email_link.rb:

class EmailLink < ApplicationRecord
belongs_to :user
after_create :send_mail
def self.generate(email)
user = User.find_by(email: email)
return nil if !user
create(user: user, expires_at: Date.today + 1.day, token: generate_token)
end
def self.generate_token
Devise.friendly_token.first(16)
end
private
def send_mail
EmailLinkMailer.sign_in_mail(self).deliver_now
end
end

Let’s generate the controller to trigger the previous callback when the user gives us their email and sends the form, rails g controller EmailLinks new create.

Let’s add the necessary routes in our app/config/routes.rb.

root 'welcome#index'
get 'email_links/new', as: :new_magic_link
post 'email_links/create', as: :magic_link
get 'email_links/validate', as: :email_link

Now, let’s give functionality to our controller:

class EmailLinksController < ApplicationController
def new
end def create
@email_link = EmailLink.generate(params[:email])
if @email_link
flash[:notice] = "Email sent! Please, check your inbox."
redirect_to root_path
else
flash[:alert] = "There was an error, please try again!"
redirect_to new_magic_link_path
end
end
def validate
email_link = EmailLink.where(token: params[:token]).where("expires_at > ?", DateTime.now).first
unless email_link
flash[:alert] = "Invalid or expired token!"
redirect_to new_magic_link_path
end
sign_in(email_link.user, scope: :user)
redirect_to root_path
end
end

At this point, our program is capable of generating the token, validating it and logging in. What we have left is to send the email and the view where the user gives us their email.

NOTE: If you want to see these alerts, you must add them in app/views/layouts/application.html.erb, inside the body tag:

<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

Sending the email

Very easy, first we are going to generate our mailer, $ rails g mailer EmailLinkMailer and then we give it functionality:

class EmailLinkMailer < ApplicationMailer
def sign_in_mail(email_link)
@token = email_link.token
@user = email_link.user
mail to: @user.email, subject: "Here is your magic link! 🚀"
end
end

Let’s edit our email (app/views/email_link_mailer/sign_in_mail.html.erb) a little:

<p>Hello, <%= @user.email %>!</p>
<p>Recently someone requested a link to enter your account, if it was you, just press the button below to log in</p>
<%= link_to "Sign in to my account", email_link_url(token: @token) %>

Brilliant! At this point our program is already capable of sending the email, we just need the main view, where the user will give us their email and we can fire all this backend.

Just add this simple form in app/views/email_links/new.html.erb:

<%= form_with(url: magic_link_path, method: :post) do %>
<%= label_tag :email, "E-mail address" %>
<%= email_field_tag :email, nil, placeholder:"carpintinimatias@gmail.com", autofocus: true, required: true %>
<%= submit_tag "Send!" %>
<% end %>

To validate on the front that this works, we can add this dummy example in app/views/welcome/index.html.erb:

<% if user_signed_in? %>
<p>Hello, <%= current_user.email %></p>
<%= link_to "Sign out", destroy_user_session_path, method: :delete %>
<% else %>
<p>Hey, Sign In with Magic Links!</p>
<%= link_to "Click here", new_magic_link_path %>
<% end %>

All right, that’s it. Thanks for reading. 👋

--

--