v-crn Code Log

主に備忘録

OmniAuthによるTwitter認証機能の実装 | Rails | Devise | email取得

f:id:v-crn:20190826153140p:plain

前提

  • Ruby 2.5.3 / Rails 5.2.3
  • DeviseでUserモデルを作成済み
  • 個々のユーザーをemailで識別する
  • Terms of ServiceとPrivacy Policyを用意できる

OmniAuthについて

TwitterFacebookなどのSNSアカウントを通じてWebアプリケーションのログイン認証を行うことができるgemです。

今回はTwitter認証のためにomniauthとomniauth-twitter、omniauth-rails_csrf_protectionという3つのgemを使用します。omniauth-rails_csrf_protectionはクロスサイトリクエストフォージェリという脆弱性への対策として導入します。

GitHub - omniauth/omniauth: OmniAuth is a flexible authentication system utilizing Rack middleware.

GitHub - cookpad/omniauth-rails_csrf_protection: Provides CSRF protection on OmniAuth request endpoint on Rails application.

GitHub - arunagw/omniauth-twitter: OmniAuth strategy for Twitter

Twitter APIの準備

Developerアカウント登録

Developer — Twitter Developers

アプリの作成

Callback URL

Callback URLs — Twitter Developers 公式によるとlocalhostはcallback URLとして使っちゃダメとのこと(何故?)。開発中はlocalhost:3000じゃなくて127.0.0.1:3000を使うと良いのか。

Don’t use localhost as a callback URL Instead of using localhost, please use a custom host locally or http(s)://127.0.0.1.

Terms of service URL / Privacy Policy URL

Twitter APIでemailを取得するにはTerms of service(利用規約) URLとPrivacy Policy URLの登録が必要。emailを扱う場合はこれらのページを準備しておきましょう。

参考

Webサービス個人開発するなら知りたい利用規約とプライバシーポリシーの作り方 - Qiita

掲示版・SNS系向きの利用規約の雛形(ひな型) | Webサイトの利用規約(無料テンプレート・商用利用可)

プライバシーポリシーの雛形(ひな型) | Webサイトの利用規約(無料テンプレート・商用利用可)

ここからRails側での作業に入ります。

Gemfile

gem 'omniauth'
gem "omniauth-rails_csrf_protection"
gem 'omniauth-twitter'

編集後、bundleを実行します。

devise.rb

Developerサイトで用意したアプリのAPIキーはアプリ詳細ページのKeys and tokens > Consumer API keysに記載されています。これらのキーをdevise.rbに設定します。

config/initializers/devise.rb

  config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET_KEY']

環境変数ENVはdotenvを利用して定義します。

.env

TWITTER_API_KEY=''
TWITTER_API_SECRET_KEY=''

忘れぬうちに本番環境にも環境変数をセットしておきましょう。

$ heroku config:set TWITTER_API_KEY=''
$ heroku config:set TWITTER_API_SECRET_KEY=''

user.rb

今回は次の要件を踏まえて実装します。 - Userモデルの作成にemailとpasswordが必須(deviseのデフォルト設定) - Twitterアカウントの表示名、アイコン画像、プロフィールをアプリのユーザーアカウントに反映させる

Usersテーブルの主なカラムは次の通り。 - email - password - name - icon - profile - provider - uid

既存のUserモデルに必要なカラムをマイグレーションで加えておきます。

$ rails g migration add_columns_to_users provider uid

deviseメソッドに:omniauthableを追加

devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :confirmable, :omniauthable

User.rbにfind_or_create_from_authメソッドを定義

認証時、TwitterアカウントのemailがDBのUsersテーブルに存在すればそのユーザーのオブジェクトを返し、なければTwitterアカウントの情報を用いてユーザーを新規作成、DBに保存するメソッドを定義します。この例ではオプションとしてアイコン画像とプロフィール文、SNSプロバイダー名、固有IDを引き継いでいます。

  def self.find_or_create_from_auth(auth)
    find_or_create_by(email: auth.info.email) do |user|
      user.password = Devise.friendly_token[0, 20]
      user.name = auth.info.name
      user.remote_icon_url = auth.info.image
      user.profile = auth.info.description
      user.provider = auth.provider
      user.uid = auth.uid
      user.skip_confirmation!
    end
  end
  • user.remote_icon_urlについて:carrierwaveの画像アップローダーを用意している場合、remote_画像のカラム名_urlという形式でWeb上の画像URLを格納した上でモデルのインスタンスを新規作成すると、アップローダーがリンク先から画像をダウンロードしてストレージにアップロードしてくれます。
  • user.skip_confirmation!SNS認証を通じたユーザー登録の際、メールアドレスの確認をスキップするために記述しています。ちなみにこのメソッドが行っているのは、ユーザーデータのconfirmed_atへの現在時刻の入力、保存です。

routes.rb

devise_for :users, controllers: {
  omniauth_callbacks: 'users/omniauth_callbacks'
}

omniauth_callbacks_controller

認証を行うためのコントローラーomniauth_callbacks_controllerを用意します。

$ rails g controller users/omniauth_callbacks

以下のようにコントローラーを編集します。 app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < ApplicationController
  def twitter
    callback_from :twitter
  end

  def failure
    redirect_to new_user_registration_url
  end

  private

  def callback_from(provider)
    provider = provider.to_s
    @user = User.find_or_create_from_auth(request.env['omniauth.auth'].except('extra'))

    if @user.persisted? # DBに保存済みかどうかを判定
      flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize)
      sign_in_and_redirect @user, event: :authentication
    else
      session["devise.#{provider}_data"] = request.env['omniauth.auth'].except('extra')
      redirect_to new_user_session_url
    end
  end
end

Viewの実装

例えば以下のようにログインページやユーザー登録ページにSNS認証ボタンを設置します。 app/views/devise/registrations/new.html.erb

<h3>SNS Authentication</h3>
<%= button_to 'Twitter', user_twitter_omniauth_authorize_path %>

このとき注意しなければならないのは認証にGETメソッドを使うべきでないという点です。 クロスサイトリクエストフォージェリの対策としてプロバイダへのリンクには、link_toならmethod: :postを付ける、あるいはbutton_toを使用するなどしてPOSTリクエストを必ず使用するようにしましょう。

Test

Railsでomniauthを使ったログイン機構の統合テスト - 気持ち

とりあえず用意した簡単なテストですが一例としてはこんな感じ。

test/fixtures/users.yml

john:
  email: "john@example.com"
  encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>
  confirmed_at: <%= Time.now - 100 %>
  provider: 'twitter'
  uid: '123456'

test/integration/users_login_test.rb

class UsersLoginTest < ActionDispatch::IntegrationTest
  include Devise::Test::IntegrationHelpers

  def setup
    Warden.test_mode!
    @user = users(:john)

    # OmniAuth configuration
    OmniAuth.config.test_mode = true
    OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new(
      provider: 'twitter',
      uid: '123456',
      info: { email: 'john@example.com', name: 'John English' }
    )
  end

  test 'login and logout with twitter account' do
    get new_user_session_path
    assert_select 'form[action=?]', user_twitter_omniauth_authorize_path
    post '/users/auth/twitter', params: OmniAuth.config.mock_auth[:twitter]
    assert_redirected_to '/users/auth/twitter/callback'
    follow_redirect!
    assert_redirected_to root_path
    follow_redirect!
    assert_select 'a[href=?]', destroy_user_session_path
    delete destroy_user_session_path
    assert_nil session[:user_id]
  end
end

認証機能の確認

ブラウザから127.0.0.1:3000にアクセスします。
問題がなければlocalhost:3000のページと同じViewが表示されるはずです。 そこからログイン画面に移動してTwitter認証リンクボタンを押してみましょう。 正常にリダイレクトされ、ログインに成功すれば勝利は目前です。

Herokuデプロイ

$ heroku pg:reset DATABASE
$ bundle exec rails assets:precompile
$ git push heroku
$ heroku run rails db:migrate

herokuへのデプロイが完了したらアプリのWeb URLにアクセスし、Twitter認証機能をチェックしてみてください。

無事、Twitter認証に成功したでしょうか?
もしそうであればあなたの勝利です。おつかれさまでした。

それともエラーに出くわしましたか?
ひとまずリフレッシュしてください。それから関連するファイルを見直したり、自分の理解した内容を整理してみましょう。諦めなければきっと解決の糸口が見つかるはずです。