Adding Magic Links to Rails 8 Authentication

This article was originally publish on Build a SaaS by Rails Designer Let me cut to the chase: I am not a fan of “passwordless” or “magic links”. It's slower, annoying and prone to pull you out of the zone (because that one email suddenly needed an answer). No, for me, a password manager is at least one factor faster. But, I am self-aware enough, to know that I am not the average web-app user. Also they're simpler to implement than you might think, and they solve a bunch of common authentication headaches for the common internet user. This article builds on top of basic Rails 8 authentication. See all the previous commits in this repo. First, you'll need a simple signup form. Here's what that looks like: Nothing fancy—just an email field and a submit button. The invisible_captcha is there to keep the bots away (more on that in my spam prevention article). The real magic happens in the MagicSignin class: # app/models/magic_signin.rb class MagicSignin AUTO_GENERATED_PASSWORD = SecureRandom.hex(32) include ActiveModel::Model include ActiveModel::Attributes attribute :email_address, :string validates :email_address, presence: true validates :email_address, is_not_spam: true def save return unless valid? User.where(email_address: email_address).first_or_create.tap do |user| if user.new_record? user.password = AUTO_GENERATED_PASSWORD user.save create_workspace_for user end send_magic_link_to user end end end This class handles both new signups and existing users. If it's a new user, it creates an account with a random password (they'll never need it) and sets up their workspace. Either way, it sends them a magic link. Speaking of magic links, you'll need to add token support to your User model: # app/models/user.rb class User < ApplicationRecord + + generates_token_for :signin, expires_in: 5.minutes end This gives you secure, time-limited tokens for your magic links. Five minutes is usually enough time for users to click the link in their email. The magic link email itself is straightforward: # app/mailers/magic_signup_mailer.rb class MagicSignupMailer

May 7, 2025 - 18:34
 0
Adding Magic Links to Rails 8 Authentication

This article was originally publish on Build a SaaS by Rails Designer

Let me cut to the chase: I am not a fan of “passwordless” or “magic links”. It's slower, annoying and prone to pull you out of the zone (because that one email suddenly needed an answer). No, for me, a password manager is at least one factor faster. But, I am self-aware enough, to know that I am not the average web-app user. Also they're simpler to implement than you might think, and they solve a bunch of common authentication headaches for the common internet user.
This article builds on top of basic Rails 8 authentication. See all the previous commits in this repo.

First, you'll need a simple signup form. Here's what that looks like:

<%# app/views/magic_signups/new.html.erb %>
<%= form_with model: @signup, url: magic_signups_path do |form| %>
  <%= form.email_field :email_address,
      required: true,
      autofocus: true,
      autocomplete: "username",
      placeholder: "Enter your email address" %>
<%= invisible_captcha %> <%= form.submit "Sign up" %> <% end %>

Nothing fancy—just an email field and a submit button. The invisible_captcha is there to keep the bots away (more on that in my spam prevention article).

The real magic happens in the MagicSignin class:

# app/models/magic_signin.rb
class MagicSignin
  AUTO_GENERATED_PASSWORD = SecureRandom.hex(32)

  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :email_address, :string
  validates :email_address, presence: true
  validates :email_address, is_not_spam: true

  def save
    return unless valid?

    User.where(email_address: email_address).first_or_create.tap do |user|
      if user.new_record?
        user.password = AUTO_GENERATED_PASSWORD

        user.save

        create_workspace_for user
      end

      send_magic_link_to user
    end
  end
end

This class handles both new signups and existing users. If it's a new user, it creates an account with a random password (they'll never need it) and sets up their workspace. Either way, it sends them a magic link.

Speaking of magic links, you'll need to add token support to your User model:

# app/models/user.rb
class User < ApplicationRecord
+
+  generates_token_for :signin, expires_in: 5.minutes
end

This gives you secure, time-limited tokens for your magic links. Five minutes is usually enough time for users to click the link in their email.

The magic link email itself is straightforward:

# app/mailers/magic_signup_mailer.rb
class MagicSignupMailer < ApplicationMailer
  def magic_link(user)
    @user = user

    mail to: @user.email_address
  end
end

With a simple template:

<%# app/views/magic_signup_mailer/magic_link.html.erb %>

Here is your magic link: <%= magic_session_url(@user.generate_token_for(:signin)) %>

(of course you want to extend the message in the email a bit—we are not savages!