Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
11
Help us understand the problem. What is going on with this article?
@tegnike

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

More than 1 year has passed since last update.

したいこと

以下の記事(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モデルではこいつらが必要なようです(当たり前といえばそうか)。。
スクリーンショット 2019-09-06 8.03.21.png

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

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

参考

11
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
tegnike
ポーランドでフルリモートエンジニア。使用言語はJava、Ruby on Rails。ゲームする感覚でプログラミングできるようになりたい。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
11
Help us understand the problem. What is going on with this article?