How to create Magic Links to authenticate users
using Ruby on Rails and Devise
Post by Pedro Resende on the 3 of June of 2025 at 18:20
Tags: devdevelopmenttutorialrorrailsrubyRuby-On-Rails

This week, I've decided to investigate how to generate magic links to authenticate users on a Ruby on Rails app.
The idea is to send a link to the user's email, which they can click to login without needing a password.
Let's start by installing a clean Rails 8.0.2 environment for this example:
rails new magic_links
Let's install the devise
gem to handle user authentication:
bundle add devise
Next, we need to run the generator to set up Devise:
rails generate devise:install
Now, let's create a User model with Devise:
rails generate devise User
After running the generator, we need to run the migrations to create the users table:
rails db:migrate
Now, we need to add a new column to the User model to store the magic link token. We can do this by creating a new migration:
rails generate migration AddMagicLoginTokenToUsers magic_login_token:string magic_login_token_sent_at:datetime
rails db:migrate
Now, let's create a new controller called magic_links
under, app/controllers
.
rails generate controller MagicLinks show
if you open the file, app/controllers/magic_links_controller.rb
, you should have the following
class MagicLinksController < ApplicationController
def show
end
end
Let's modify the controller to look like this:
class MagicLinksController < ApplicationController
def show
@user = User.find_by(magic_login_token: params[:id])
if @user && @user.magic_login_token_sent_at > 10.minutes.ago
sign_in(@user)
redirect_to root_path, notice: "Successfully logged in!"
else
redirect_to new_user_session_path, alert: "Invalid or expired magic link."
end
end
def create
user = User.find_by(email: params[:email])
if user
user.generate_magic_login_token!
UserMailer.magic_link(user).deliver_now
redirect_to new_user_session_path, notice: "Magic link sent to your email."
else
redirect_to new_user_session_path, alert: "Email not found."
end
end
def new
# This action will render the form to request a magic link
end
end
Next, we need to add the generate_magic_login_token!
method to the User model. Open the file app/models/user.rb
and add the following method:
class User < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
def generate_magic_login_token!
self.magic_login_token = SecureRandom.hex(20)
self.magic_login_token_sent_at = Time.current
save!
end
def magic_login_token_valid?
magic_login_token_sent_at > 15.minutes.ago
end
def clear_magic_login_token!
update!(magic_login_token: nil, magic_login_token_sent_at: nil)
end
end
Now, we need to create a mailer to send the magic link to the user. Let's generate a mailer called UserMailer
:
rails generate mailer UserMailer
This will create a new file in app/mailers/user_mailer.rb
. Open that file and add the following code:
class UserMailer < ApplicationMailer
def magic_link(user)
@user = user
@magic_link = magic_link_url(@user.magic_login_token)
mail(to: @user.email, subject: "Your Magic Login Link")
end
end
Now, we need to create a view for the mailer. Create a new file in app/views/user_mailer/magic_link.html.erb
and add the following code:
<h1>Magic Login Link</h1>
<p>Hello <%= @user.email %>,</p>
<p>Click the link below to log in:</p>
<p><%= link_to "Log in", @magic_link %></p>
<p>If you did not request this, please ignore this email.</p>
Now, we need to add the routes for the magic links. Open the file config/routes.rb
and add the following code:
Rails.application.routes.draw do
devise_for :users
resources :magic_links, only: [ :show, :create, :index ]
root "home#index" # Assuming you have a home controller with an index action
end
Finally, we need to create a form for the user to request a magic link. Create a new file in app/views/magic_links/new.html.erb
and add the following code:
<h1>Request Magic Link</h1>
<%= form_with url: magic_links_url, method: :post do |form| %>
<%= form.email_field :email, placeholder: "Email", class: "mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" %>
<%= form.submit "Send Magic Link", class: "inline-flex items-center px-4 py-2 bg-blue-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-blue-500 active:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
<% end %>
Now, you can navigate to /magic_links
to request a magic link. When the user submits their email, the app will generate a magic link and send it to their email address.
When the user clicks the magic link, they will be redirected to the MagicLinksController#show
action, which will authenticate them and log them in.
This implementation allows users to log in without a password by clicking a magic link sent to their email.
The link is valid for 15 minutes, and the user can only use it once.
That is it, please let me know if you propose a different approach.