Ruby on RailsでMagic Link的な認証を作ってみたく調べてたところpasswordless
というgemを見つけて色々触ってみたので使い方とかをメモしておきます📝
Magic Linkとは?
サイトに用意された「マジックリンク」と名付けられたボタンを押します。すると、登録したメールアドレスにログインのためのURLが送られ、それをクリックすることでパスワードを入力することなく、ログイン処理が行えるのです。 ユーザーが持つ「メールアドレス」を鍵としたログインの仕組みです。
Slackで使われているアレですね👀 ユーザーがパスワードを覚えなくてもいいのも良いですが、サービスとしてパスワードを抱えなくても良いというのも運営側からするとメリットなのかなと思いました。
passwordlessの使い方
上記のMagic LinkをRailsで実現するためのgemがpasswordless
です。
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) %>
ユーザー作成と合わせてマジックを送信したい
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
マジックリンク送信後のリダイレクト先を変えたい
先程の例と同様ですが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
おわりに
パスワードレスコードも割と読みやすくでコアなロジックが切り出してあり、色々カスタマイズしやすい感じで良いですね✨