OmniAuthによるTwitter認証機能の実装 | Rails | Devise | email取得
- 前提
- OmniAuthについて
- Twitter APIの準備
- Gemfile
- devise.rb
- user.rb
- routes.rb
- omniauth_callbacks_controller
- Viewの実装
- Test
- 認証機能の確認
- Herokuデプロイ
前提
- Ruby 2.5.3 / Rails 5.2.3
- DeviseでUserモデルを作成済み
- 個々のユーザーをemailで識別する
- Terms of ServiceとPrivacy Policyを用意できる
OmniAuthについて
TwitterやFacebookなどの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 - 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認証に成功したでしょうか?
もしそうであればあなたの勝利です。おつかれさまでした。
それともエラーに出くわしましたか?
ひとまずリフレッシュしてください。それから関連するファイルを見直したり、自分の理解した内容を整理してみましょう。諦めなければきっと解決の糸口が見つかるはずです。