Last updated on December 20, 2021
Passwordless authentication is a verification process that determines whether someone is, in fact, who they say they are without requiring that person to enter their username and password.
Instead you use a security token to authenticate the user.
Here are some benefits of passwordless auth:
Even though that I am a big fan of Devise, I started to like how simple and clean Sorcery is. It doesn’t take much code to get Sorcery up and running, especially if you are implementing passwordless authentication.
This is the basic premise of using email to authenticate the user
Our goal in this article is to create a passwordless authentication system for the User model and send the user an email containing a magic link. When a user click on the magic link, they are automatically signed in.
Pay attention to the login_token and the login_token_valid_until - we will use those to store information about the passwordless auth.
rails g controller users index edit update
rails g model User name:string email:string login_token:string login_token_valid_until:datetime
rake db:migrate
In these controllers, we will validate the token and login the user.
rails g controller logins create
rails g controller sessions create destroy
Rails.application.routes.draw do
#
get 'sessions/create'
delete 'sessions/destroy'
#
get 'logins/new'
post 'logins/create'
#
resources :users
#
root 'users#index'
end
Let’s go to Github and see how to configure Sorcery, in order to link it to the User model.
We won’t need to use all the steps in the example, because we don’t want passwords.
In the Gemfile, include
gem 'sorcery'
and in the terminal execute the command
bundle install
Next, we hook Sorcery to the User model by simple writing
class User < ApplicationRecord
authenticates_with_sorcery!
end
Update Application Controller to configure current_user.
We also let rails know what should happen if the user is not authenticated successfully.
class ApplicationController < ActionController::Base
def user_class
User
end
def not_authenticated
redirect_to root_path, alert: 'Not authenticated'
end
end
Finally, include require_login in the UsersController in order to let Sorcery know which actions require authentication.
class UsersController < ApplicationController
before_action :require_login, only: [:edit, :update]
def index
end
def edit
end
def update
end
end
Install the simple form gem
gem 'simple_form'
In logins/new.html.erb, we define a simple form that submits an email address.
<%= simple_form_for @user, url: logins_create_path, method: :post do |f| %>
<%= f.input :email, label: "Email" %>
<%= f.submit "Get a magic link" %>
<% end %>
Next, let’s update our LoginsController to look like this
class LoginsController < ApplicationController
def new
@user = User.new
end
def create
# the user might already exist in our db or it might be a new user
user = User.find_or_create_by!(email: params[:user][:email])
# create a login_token and set it up to expiry in 60 minutes
user.update!(login_token: SecureRandom.urlsafe_base64,
login_token_valid_until: Time.now + 60.minutes)
# create the url which is to be included in the email
url = sessions_create_url(login_token: user.login_token)
# send the email
LoginMailer.send_email(user, url).deliver_later
redirect_to root_path, notice: 'Login link sent to your email'
end
end
We defined the LoginMailer, now we need to create it.
Create a file in app/mailers/login_mailer that will look like this
class LoginMailer < ApplicationMailer
def send_email(user, url)
@user = user
@url = url
mail to: @user.email, subject: 'Sign in into mywebsite.com'
end
end
We need to create the view of the letter. Create a new file app/views/login_mailer/send_email.html.erb
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1>Welcome <%= @user.email %>,</h1>
<a href="<%= @url %>">Magic link</a>
</html>
class SessionsController < ApplicationController
def create
# we don't log in the user if a login token has expired
user = User.where(login_token: params[:login_token])
.where('login_token_valid_until > ?', Time.now).first
if user
# nullify the login token so it can't be used again
user.update!(login_token: nil, login_token_valid_until: 1.year.ago)
# sorcery helper which logins the user
auto_login(user)
redirect_to root_path, notice: 'Congrats. You are signed in!'
else
redirect_to root_path, alert: 'Invalid or expired login link'
end
end
def destroy
# sorcery helper which logouts the user
logout
redirect_to root_path, notice: 'You are signed out'
end
end
Congrats! You have a passwordless authentication
Let’s finish this article by actually doing something with the passwordless auth.
Update your UsersController which allows us to update the user name.
class UsersController < ApplicationController
before_action :require_login, only: [:edit, :update]
def index
end
def edit
end
def update
current_user.update! user_params
redirect_to root_path
end
private
def user_params
params.require(:user).permit(:name)
end
end
Update views/users/index.html.erb
<% if current_user %>
<h1><%= current_user.name.present? ? "My name is #{current_user.name}" : "Input your name using the link below" %></h1>
<%= link_to "Edit current user", edit_user_path(current_user) %> <br>
<%= link_to "Sign out", sessions_destroy_url, method: :delete %> <br>
<% else %>
<h1>Login to input your name</h1>
<%= link_to "Login to edit", logins_new_path %>
<% end %>
and finally views/users/edit.html.erb
<%= simple_form_for current_user do |f| %>
<%= f.input :name, label: "Name" %>
<%= f.submit "Update name" %>
<% end %>
To make sure that the authentication works, go to incognito and enter the edit page URL. You shouldn’t be able to access the page unless you are logged in with the correct account.
Articles, guides and interviews about web development and career progression.
Max 1-2x times per month.