【Ruby on Rails】DeviseなしのUserモデルにOauth認証を追加する方法


Oauth認証とは

Open Authorization 認証の略です。読み方は「オーオース」らしい。
ざっくりいうとTwitterやFacebookのアカウント情報でログインできるようになります。
アイコン画像も取ってこれるので、ユーザーとしては登録の手間がかなり省けて便利です。

まずはAPIを取得する必要がある。

こちらを参考にDeveloperとして登録します。しかし、Describe in your own words what you are buildingの欄のみリンクと違い、4つの質問に一つ一つ答えていく形式に変わっています。(2019年4月10日現在)

大まかに日本語に訳すと、以下の事柄について聞かれています。
すべて合わせて、300文字以上書く必要があります。

1.どのような目的やケースでツイッターのAPIを使うのか
2.ツイートやユーザーなどを分析するつもりかどうか。もしするなら、その方法や技術と詳細を記述。
3.ツイート、リツイート、リンクを含んだ使い方をするかどうか。もしするなら、ユーザーやコンテンツをやりとりする方法を記述。
4.どのようにツイッターの情報が表示されるのか。もしツイッターからコンテンツを表示するなら、アプリのどこにどのように表示されるのかを記述(それぞれのツイートやツイッターのコンテンツが表示されるのか、それともまとめた情報が表示されるのか)

Developerの登録が終わったら、こちらにいき、右上の「Create an app」からAPIを使うアプリを登録します。
「App name」や「Website URL」は特に問題ないですが、Callback URLsに関してはURLが2つ以上登録されていないと正しく動作しません。
ローカル環境の場合は以下の2つのURLを登録します。

http://127.0.0.1:3000/auth/twitter/callback
http://localhost:3000/auth/twitter/callback

また本番環境のときには、localhost:3000や127.0.0.1:3000の部分をドメイン名に書き換えたURLを一つ追加すればOKです。

次にKeys and tokensからConsumer API keysをを取得します。

このAPI KeyとAPI secret keyを後で使用します。
Access token & access token secretについては認証やツイートを取得するだけであれば必要ありません。

gemのインストール

各種gemをインストールしていきます。

twitter認証用のgem

Gemfile
gem 'omniauth'
gem 'omniauth-twitter'

先ほどのConsumer API keysはGitHubに上げたくないので、環境変数に入れます。
今回は.bash_profileなどには入れず、gemで管理したいと思います。

Gemfile
gem 'dotenv-rails'

bundleします。

設定

まずは環境変数の設定をします。
直下の.env(なければ作成)に以下を追記します。

.env
TWITTER_CONSUMER_KEY = (先ほど取得したAPI Key)
TWITTER_CONSUMER_SECRET = (先ほど取得したAPI secret key)

.env.gitignoreに追加するのを忘れないようにしてください。

route.rbに以下を追記します

route.rb
get 'auth/:provider/callback', to: 'sessions#create'

次にconfig/initializers/omniauth.rbを作成し、以下を設定

config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
    provider :twitter, ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET'],
end

実装

次にuserモデルにuid, provider、もしなければimageのためのカラムも追加します。
providerで何のサービスかを判断し、uidでどのユーザーかを識別しています。

add_columns_to_users.rb
class AddColumnsToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :uid, :string
    add_column :users, :provider, :string
    add_column :users, :image_url, :string
  end
end

rails db:migrate後、Userモデルには以下を追記します。

user.rb
  def self.find_or_create_from_auth(auth)
    provider = auth[:provider]
    uid = auth[:uid]
    name = auth[:info][:name]
    image = auth[:info][:image]

    self.find_or_create_by(provider: provider, uid: uid) do |user|
      user.name = name
      user.image_url = image
    end
  end

self.find_or_create_by(provider: provider, uid: uid) do |user|
でcreateが行われていた場合に検証やコールバックが行われるので適時修正する必要があります。

自分の場合は

user.rb
  before_save { self.email.downcase! }
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
  has_secure_password

user.rb
  before_save :email_downcase, unless: :uid?
  validates :name, presence: true, unless: :uid?, length: { maximum: 50 }
  validates :email, presence: true, unless: :uid?, length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
  has_secure_password validations: false

  private

  def email_downcase
    self.email.downcase!
  end

のように修正することで上手くいきました。unless: :uidを使用するときには処理内容をメソッドとして切り分けないとsyntax errorになります。

次にsessions_controllersを以下のように修正します。

sessions_controller.rb
  def create
    auth = request.env['omniauth.auth']
    if auth.present?
      user = User.find_or_create_from_auth(request.env['omniauth.auth'])
      session[:user_id] = user.id
      redirect_to user
    else
      (従来のログイン処理)
    end
  end

次に画像の表示の仕方です。
自分の場合はメールアドレスに対応してgravatarのアイコンが表示される実装になっていました。
(プログラミングスクールやRailsチュートリアルではよくある実装だと思います)
そこで、画像を表示するヘルパーを以下に修正

users_helper.rb
module UsersHelper
  def icon_url(user, options = { size: 80 })
    if user.email.present?
      gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
      size = options[:size]
      return "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    else
      return user.image_url
    end
  end
end

最後にビューの実装です。

<%= link_to "Twitterアカウントでログイン", "/auth/twitter" %>

まとめ

はまるポイントとしてはCallback URLsが複数必要なこととcreateで検証やコールバックでつまづくところでしょうか。同じ要領で他のSNSのOauth認証もやっていきたいと思います。