Implement a passwordless authentication with Sorcery
Last updated on December 20, 2021
What is passwordless authentication?
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:
- improved UX - the user doesn't need to remember passwords and request new password if they forget their old one
- increased security - reduced chance of attacks, password re-use and leaks
- relatively simple to create - you don't need to create a lot of views
What is Sorcery?
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.
Authentication through email
This is the basic premise of using email to authenticate the user
- a user inputs their email on an input link
- we send an email with a magic link (one time, randomly generated code)
- The user clicks the link and the service being used will identify the token and exchange it for a live token, logging the user in.
Enough theory, let's get to coding
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.
Create a user controller and model
Pay attention to the login_token and the login_token_valid_until - we will use those to store information about the passwordless auth.
1rails g controller users index edit update2rails g model User name:string email:string login_token:string login_token_valid_until:datetime3rake db:migrate
1rails g controller logins create2rails g controller sessions create destroy
1Rails.application.routes.draw do2#3get 'sessions/create'4delete 'sessions/destroy'56#7get 'logins/new'8post 'logins/create'910#11resources :users1213#14root 'users#index'15end
Configure Sorcery
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
1gem 'sorcery'
and in the terminal execute the command
1bundle
Next, we hook Sorcery to the User model by simple writing
1class User < ApplicationRecord2authenticates_with_sorcery!3end
Update Application Controller to configure current_user.
We also let rails know what should happen if the user is not authenticated successfully.
1class ApplicationController < ActionController::Base2def user_class3User4end56def not_authenticated7redirect_to root_path, alert: 'Not authenticated'8end9end
Finally, include require_login in the UsersController in order to let Sorcery know which actions require authentication.
1class UsersController < ApplicationController2before_action :require_login, only: [:edit, :update]3def index4end56def edit7end89def update10end11end
1gem 'simple_form'
In logins/new.html.erb, we define a simple form that submits an email address.
1<%= simple_form_for @user, url: logins_create_path, method: :post do |f| %>2<%= f.input :email, label: "Email" %>3<%= f.submit "Get a magic link" %>4<% end %>
Next, let's update our LoginsController to look like this
1class LoginsController < ApplicationController2def new3@user = User.new4end56def create7# the user might already exist in our db or it might be a new user8user = User.find_or_create_by!(email: params[:user][:email])910# create a login_token and set it up to expiry in 60 minutes11user.update!(login_token: SecureRandom.urlsafe_base64,12login_token_valid_until: Time.now + 60.minutes)1314# create the url which is to be included in the email15url = sessions_create_url(login_token: user.login_token)1617# send the email18LoginMailer.send_email(user, url).deliver_later1920redirect_to root_path, notice: 'Login link sent to your email'21end22end
1class LoginMailer < ApplicationMailer2def send_email(user, url)3@user = user4@url = url56mail to: @user.email, subject: 'Sign in into mywebsite.com'7end8end
We need to create the view of the letter. Create a new file app/views/login_mailer/send_email.html.erb
1<!DOCTYPE html>2<html>3<head>4<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />5</head>6<body>7<h1>Welcome <%= @user.email %>,</h1>8<a href="<%= @url %>">Magic link</a>9</html>
1class SessionsController < ApplicationController2def create3# we don't log in the user if a login token has expired4user = User.where(login_token: params[:login_token])5.where('login_token_valid_until > ?', Time.now).first67if user8# nullify the login token so it can't be used again9user.update!(login_token: nil, login_token_valid_until: 1.year.ago)1011# sorcery helper which logins the user12auto_login(user)1314redirect_to root_path, notice: 'Congrats. You are signed in!'15else16redirect_to root_path, alert: 'Invalid or expired login link'17end18end1920def destroy21# sorcery helper which logouts the user22logout2324redirect_to root_path, notice: 'You are signed out'25end26end
1class UsersController < ApplicationController2before_action :require_login, only: [:edit, :update]3def index4end56def edit7end89def update10current_user.update! user_params11redirect_to root_path12end1314private15def user_params16params.require(:user).permit(:name)17end18end
Update views/users/index.html.erb
1<% if current_user %>2<h1><%= current_user.name.present? ? "My name is #{current_user.name}" : "Input your name using the link below" %></h1>3<%= link_to "Edit current user", edit_user_path(current_user) %> <br>4<%= link_to "Sign out", sessions_destroy_url, method: :delete %> <br>5<% else %>6<h1>Login to input your name</h1>7<%= link_to "Login to edit", logins_new_path %>8<% end %>
and finally views/users/edit.html.erb
1<%= simple_form_for current_user do |f| %>2<%= f.input :name, label: "Name" %>3<%= f.submit "Update name" %>4<% 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.
Level up your web development skills
Get articles, guides and interviews right in your inbox. Join a community of fellow developers.
No spam. Unsubscribe at any time.