Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby on Rails: passwordlessを使ってMagicLink的な認証機能を作る

Ruby on RailsでMagic Link的な認証を作ってみたく調べてたところpasswordlessというgemを見つけて色々触ってみたので使い方とかをメモしておきます📝

github.com

Magic Linkとは?

サイトに用意された「マジックリンク」と名付けられたボタンを押します。すると、登録したメールアドレスにログインのためのURLが送られ、それをクリックすることでパスワードを入力することなく、ログイン処理が行えるのです。 ユーザーが持つ「メールアドレス」を鍵としたログインの仕組みです。

www.itmedia.co.jp

Slackで使われているアレですね👀 ユーザーがパスワードを覚えなくてもいいのも良いですが、サービスとしてパスワードを抱えなくても良いというのも運営側からするとメリットなのかなと思いました。

passwordlessの使い方

上記のMagic LinkをRailsで実現するためのgemがpasswordlessです。

github.com

Rails engineを使って作成されています。

READMEに記載の通りですが、使い方は簡単でGemfileに以下を追記後bundle installを実行し、

gem 'passwordless'

passwordlessが使用するtebleと認証したいmodel(User等)を作成します。

$ bin/rails passwordless:install:migrations
$ bin/rails generate model User email
# bin/rails db:migrate

あとはModel側にpasswordlessを使用するための設定を入れて、

class User < ApplicationRecord
  validates :email, presence: true, uniqueness: { case_sensitive: false }

  passwordless_with :email # <-- here!
end

routesでpasswordless_forを使用してengineをmountします。

Rails.application.routes.draw do
  passwordless_for :users

  # other routes
end

ref: https://github.com/mikker/passwordless/blob/master/lib/passwordless/router_helpers.rb#L19

これで準備OKです👍

実際にMagic Linkを送信する場合は以下のようにユーザーを作成したあとに、

User.crete(email: 'foo@example.com')

/sign_inに遷移し、入力フォームにfoo@example.comを入力しサブミットするとマジックリンクが記載されたメールがユーザーに送信されます📧

Here's your link: http://localhost:3000/sign_in/MAGIC_LINK_TOKEN

Tips

ログイン必須にしたい

以下のような形で出来ました。マジックリンクの送信画面もログイン必須にならないようにskip_before_action :authenticate_user!, if: :passwordless_controller?を入れています。

class ApplicationController < ActionController::Base
  include Passwordless::ControllerHelpers

  before_action :authenticate_user!
  skip_before_action :authenticate_user!, if: :passwordless_controller?
  
  helper_method :current_user

  def current_user
    @current_user ||= authenticate_by_session(User)
  end

  def authenticate_user!
    return if current_user
    redirect_to sign_in_path, flash: { alert: 'ログインしてください!' }
  end
end

マジックリンク送信時のメールの文面を変更したい

app/views/passwordless/mailer/magic_link.text.erbを作成して、オーバーライドしてあげればOKです🙆‍♂️

Custom Message!!

<%= I18n.t('passwordless.mailer.magic_link', link: @magic_link) %>

railsguides.jp

ユーザー作成と合わせてマジックを送信したい

Passwordless.after_session_saveを使うと任意のタイミングでマジックリンクのURLをメールで送信出来るのでユーザー作成後に送信してあげるようにすると良さそうです。

class UsersController < ApplicationController
  skip_before_action :authenticate_user!

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    ActiveRecord::Base.transaction do
      if @user.save
        session = build_passwordless_session(@user)
        Passwordless.after_session_save.call(session, request) if session.save
        redirect_to sign_up_path, flash: { notice: 'ログイン用のリンクをメールで配信しました!' }
      else
        render :new
      end
    end
  end

  private

  def user_params
    params.require(:user).permit(:email)
  end
end

github.com

マジックリンク送信後のリダイレクト先を変えたい

先程の例と同様ですがPasswordless.after_session_saveを使うと任意のタイミングでマジックリンクのURLをメールで送信出来るので、独自のマジックリンク送信用のエンドポイントを用意してリダイレクト先を指定してあげると良さそうです。

class MagicLinksController < ApplicationController
  before_action :authenticate_user!

  def create
    session = build_passwordless_session(current_user)
    Passwordless.after_session_save.call(session, request) if session.save
    redirect_to mypage_path, flash: { notice: 'ログイン用のリンクをメールで配信しました!' }
  end
end

一度使用されたマジックリンクは無効化したい

config/initializers/passwordless.rbを用意して以下の指定をしてあげればOKです。

Passwordless.restrict_token_reuse = true

github.com

おわりに

パスワードレスコードも割と読みやすくでコアなロジックが切り出してあり、色々カスタマイズしやすい感じで良いですね✨

参考

ohbarye.hatenablog.jp

medium.com