DEVELOPMENT by Pedro Resende

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

header

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.