Edited at

deviseのTwitterログイン時はメール認証とメール送信をスキップする


したいこと

以下の記事(devise + Twitterログイン)を参考に導入したRailsアプリで、Twitterログイン時のみメール送信処理を省きたかったのですが、地味に苦戦してしまったので備忘録として残します。

[Rails] deviseの使い方(rails5版)

なお、以降の記事は上記の記事に沿って話を進めるので、一度読んでおくことを推奨します。


実装手順


1. skip_confirmation! メソッドを利用する

Twitterで認証した後の処理なので、次のコールバック用コントローラーをどうにかすれば良さそう。


app/controllers/omniauth_callbacks_controller.rb

class OmniauthCallbacksController < Devise::OmniauthCallbacksController

def twitter
@user = User.from_omniauth(request.env["omniauth.auth"].except("extra"))

if @user.persisted?
sign_in_and_redirect @user
else
session["devise.user_attributes"] = @user.attributes
redirect_to new_user_registration_url
end

end
end


skip_confirmation! というメソッドを使えばメール認証をスキップできるらしいので、以下のように else 以下を修正する。


app/controllers/omniauth_callbacks_controller.rb

class OmniauthCallbacksController < Devise::OmniauthCallbacksController

def twitter
@user = User.from_omniauth(request.env["omniauth.auth"].except("extra"))

if @user.persisted?
sign_in_and_redirect @user
else
@user.skip_confirmation!
@user.save!
# session["devise.user_attributes"] = @user.attributes
# ↑ 認証データを覚える必要はないので削除
# redirect_to new_user_registration_url
# ↑ ログインすることになるので以下のように修正
sign_in_and_redirect @user
end
end
end


ついでに記事内で作っていた self.new_with_session は必要ないので削除します。


app/models/user.rb

class User < ApplicationRecord

##省略

#↓削除
#def self.new_with_session(params, session)
# if session["devise.user_attributes"]
# new(session["devise.user_attributes"]) do |user|
# user.attributes = params
# end
# else
# super
# end
#end
end


これで行けるかなとおもったらそんなに甘くない。。 emailpassword がないよって怒られてしまいました。

知らなかったのですが、deviseが用意するUserモデルではこいつらが必要なようです(当たり前といえばそうか)。。


2. Email と Password をセットする

というわけで Email と Password をセットします。これは user.rb でレコードを作成している箇所があるので、そこについでに追加します。


app/models/user.rb

class User < ApplicationRecord

##省略

def self.from_omniauth(auth)
find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.username = auth["info"]["nickname"]
user.password = Devise.friendly_token[0,20] #<-追加
user.email = User.dumy_email(auth) #<-追加
end
end

#↓追加
private
def self.dumy_email(auth)
"#{auth.uid}-#{auth.provider}@example.com"
end
end


passwordは Devise.friendly_token メソッドでランダムな値を自動生成します。便利ですね。

さて、emailに関してですが、こちら実はTwitterに登録されているemailは取ってくることができません。 user.username = auth["info"]["email"] のようにすると nil が返って来てしまいます。正確には可能なのですが、どうやら申請が必要らしく、許可が降りるのに2~3日かかるそうです。。

そのため今回は一意のメールアドレスを登録するようにメソッドを作って設定しています。

さて、これで問題なくできました! と思ったのですが、まだ必要なことがあるっぽい。。


3. メールを飛ばないようにする

さて、実際にTwitterでログインしてみるとちゃんとリダイレクトされたページに飛びました。見た目には問題なくできていたのですが、コンソールを見てみるとなぜかメールが送信された痕跡が。。(開発環境だったので、実際にメールは飛んでいません)

Devise::Mailer#confirmation_instructions: processed outbound mail in 306.2ms

Sent mail to XXXXXXXXXXXXX-twitter@example.com (998.8ms)
Date: Fri, 06 Sep 2019 10:19:04 +0000
From: test@example.com
Reply-To: test@example.com
To: XXXXXXXXXXXXX-twitter@example.com
Message-ID: <XXXXXXXXX@XXXXXX.mail>
Subject: Confirmation instructions
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit

<p>Welcome XXXXXXXXXXXXX-twitter@example.com!</p>

<p>You can confirm your account email through the link below:</p>

<p><a href="http://localhost:3000/users/confirmation?confirmation_token=XXXXXX">Confirm my account</a></p>

??? なんで???

。。。と小一時間悩んでいたのですが、理由が解明しました。どうやらdeviseのconfirmableモジュールを利用していると、Userデータがデータベースに登録されたときにメールが飛ぶようになっているんですね。

omniauth_callbacks_controller.rb@user.save!skip_confirmation! メソッドの後に実行しているのですが、実はその前にすでにセーブされちゃっていたみたいです。

先程の user.rb では find_or_create_by メソッドを使っていますが、ここでデータが保存されてしまっていますね。。。

なので、以下のように create から initialize に変更して保存しないようにしましょう!

本家の記事では、おそらく email と password が設定されていないため保存されず、ちょうどいい感じの処理になっているんだと思います。)


app/models/user.rb

class User < ApplicationRecord

##省略

def self.from_omniauth(auth)
# find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
find_or_initialize_by(provider: auth["provider"], uid: auth["uid"]) do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.username = auth["info"]["nickname"]
user.password = Devise.friendly_token[0,20]
user.email = User.dumy_email(auth)
end
end

##省略
end


これでやっと期待通りの処理ができるようになりました!


参考