DeviseとOmniauthでtwitter,facebook ログイン機能


はじめに

自分が実装しているときに割とハマったので書きました。

deviseでuserモデルを作成するとこから始めます。

公式ドキュメント

github-devise-wiki OmniAuth: Overview

参考資料

Devise+OmniAuthでQiita風の複数プロバイダ認証-Qiita

Rails5.2から追加された credentials.yml.enc のキホン-Qiita

[Rails]gem "OmniAuth" の脆弱性対策-Qiita

secrets.ymlや環境変数をRails 5.2のEncrypted Credentialsに移行する


gem

以下のgemを追加して bundle install します。


gemfile

gem "devise"

gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-twitter'


deviseのインストール

$ rails g devise:install

      create  config/initializers/devise.rb

create config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

1. Ensure you have defined default url options in your environments files. Here
is an example of default_url_options appropriate for a development environment
in config/environments/development.rb:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

In production, :host should be set to the actual host of your application.

2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:

root to: "home#index"

3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:

<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

4. You can copy Devise views (for customization) to your app by running:

rails g devise:views

上の4つを行ってください。


userモデルの設定

userモデルを作成

$ rails g devise User

:models/user.rb

class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable
# :omniauthableを追加してください。
end

bundle install します。

omniauth用のカラムをuserモデルに追加して行きます。↓

oauth認証時にproviderの名前(facebook,twitter)、uid(providerのユーザーID)がいるみたいなので追加します。

また、image,nameも取得したいので追加します。

$ rails g migration add_columns_to_users provider uid name image

$ rake db:migrate


Twitter DeveloperとFacebook Developerのアカウント作成

すいませんが以下のサイトを見てください。詳しく書いてあります。


Twitter Developer

Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ ※2018年9月時点の情報-Qiita


Facebook Developer

【2019年】これで完璧!Facebook Developerに登録する方法を図解つきで説明


Developerでアプリを作成

アカウントを作成したら、Developerでアプリを作成してください。


Twitter Developer

Twitter Developerでは Callback URLに

https://127.0.0.1:3000/users/auth/twitter/callback(127.0.0.1:3000にはprodction用のurl)

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

この2つを記入してください。


Facebook Developer

またこのサイトを見てもらって、

【2019年】これで完璧!Facebook Developerに登録する方法を図解つきで説明

ダッシュボード下の設定→ベーシック→ウェブサイトのサイトurlに

ローカルだけで試すのであれば

http://localhost:3000

ローカルと本番環境(heroku)

自分のサイトのurl

を記入してください。

本番環境では有効なOAuthリダイレクトURIの設定が必要

facebookログイン下の設定→有効なOAuthリダイレクトURIに

https://127.0.0.1:3000/users/auth/facebook/callback(127.0.0.1:3000にはprodction用のurl)


Oauthの設定


プロバイダーの宣言

config/initializers/devise.rbでプロバイダーのkeyとsecret_keyを設定します。


config/initializers/devise.rb


Devise.setup do |config|
config.omniauth :facebook, 'App ID', 'App Secret'
config.omniauth :twitter, 'API key', 'API secret'
end

私の場合ローカルで繋がったのが確認出来るまでは直接書いちゃってます。

注意としてここに書いたままで、 githubなどのリモートリポジトリーにあげないこと。

上げるときは、dotenvfigarocredentials.yml.encなどで管理してください。


userモデルにメソッドを追加


models/user.rb


class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable

# このから下を追加--------------------------------------------------------------
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.name
user.email = auth.info.email
user.password = Devise.friendly_token[0, 20] # ランダムなパスワードを作成
user.image = auth.info.image.gsub("_normal","") if user.provider == "twitter"
user.image = auth.info.image.gsub("picture","picture?type=large") if user.provider == "facebook"
end
end
end


このメソッドはproviderand uidフィールドで既存のユーザーを見つけようとします。ユーザーが見つからない場合は、ランダムなパスワードといくつかの追加情報を使用して新しいユーザーが作成されます。 後ろにくっついているfirst_or_createメソッドにそのような機能があるみたいです。

emailはdeviseでユーザーを登録する時に必ず、必要なので追加してます。

imageについてはそのまま登録してしまうと画像が荒くなってしまいます。

twitterに関してはgsubメソッドを使用してurlの"_normai"""に置き換えてます。

facebookに関してはgsubメッソドを使用してurlの"picture""picture?type=large"に置き換えてます。

また、carrierwaveを使ってアップローダーを挟みたい場合、画像の保存先をuser.imageからuser.remote_image_urlに変更したらアップローダー経由でimageを保存できます。(ローカルでしか試していません)


コールバックの設定

どのコントローラーでOmniauthコールバックを実装するかをDeviseに伝える必要があります。


config/routes.rb


Rails.application.routes.draw do
root to: "home#index"
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

end


今回はcontrollers/users/omniauth_callbacksでコールバックを実装します。

なので次にusersの中にomniauth_callbacks コントローラーを作成します。


controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
@user = User.from_omniauth(request.env["omniauth.auth"])

if @user.persisted?
sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"].except("extra")
redirect_to new_user_registration_url
end
end

def twitter
# You need to implement the method below in your model (e.g. app/models/user.rb)
@user = User.from_omniauth(request.env["omniauth.auth"])

if @user.persisted?
sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
set_flash_message(:notice, :success, kind: "Twitter") if is_navigational_format?
else
session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra")
redirect_to new_user_registration_url
end
end

def failure
redirect_to root_path
end
end


コールバックはプロバイダと同じ名前のアクションとして実装する必要があります。(今回はtwitterとfacebook)

ここでuserモデルで作成したself.from_omniauthメソッドを使って、引数にrequest.env["omniauth.auth"]をセットしてます。

request.env["omniauth.auth"]の中に受け取ったユーザーのデータが入ってるみたいです。

persisted?メソッドでデータベースに@userのデータがあるかないかで条件分岐してます。


リンクについて

ログインへのリンクはdeviseで勝手に生成されています。

追加した:omniauthableのおかげみたい。

<%= render "devise/shared/links" %>

このコードが書いてあるログインとサインアップ画面に出てると思います。


models/user.rb


class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable # これです。
end

実際に出現しているリンクが以下のリンクです。


devise/shared/_links.html.erb


<%- if controller_name != 'sessions' %>
<%= link_to "Log in", new_session_path(resource_name) %><br />
<% end %>

<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end %>

<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end %>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end %>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end %>

<!-- --------この部分のコードです。------------------------------------------- -->
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
<% end %>
<% end %>
<!-- -------------ここまで----------------------------------------------------- -->


スクリーンショット 2019-07-08 8.32.45.png

適当で申し訳ないですが、こんな感じでリンクが出ます。

ここまで実装しますと、twitterとfacebookでログインが出来るようになります。


編集ページについて

deviseはデフォルトで編集時にパスワードが必要になります。

twitter、facebookでログインするとこの場合はパスワードをランダムで生成してログインしているため、パスワードを把握しておらず、編集が出来ないと思います。

そこで編集時にパスワードを入力をしないでいいように編集します。(ただし、パスワードの変更はそのページでは出来なくなります)

私もなんでこうなるのかとか曖昧ですのでこちらを読んでもらえると助かります。

参考リンク↓

github-devise-wiki How To: Allow users to edit their account without providing a password


ルートの作成


config/routes.rb


Rails.application.routes.draw do
root to: "home#index"
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks', registrations: 'registrations' }
# registrations: 'registrations' を追加してます。
resources :users, only: [:index, :show]
end


コントローラーの作成

上のルートに合わせてコントローラーを作成します。(生身はサイトのコピーです)


controllers/registrations_controller.rb


class RegistrationsController < Devise::RegistrationsController

protected

def update_resource(resource, params)
resource.update_without_password(params)
end
end


update_resourceメソッドをオーバーライドして、アップデート時のパスワード入力を取り除きました。

devise/registrations/edit.html.erbにcurrent_password(変更時に入力するパスワードのフォーム)が残っているため取り除きます。


devise/registrations/edit.html.erb


<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name, autofocus: true, autocomplete: "name" %>
</div>

<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autocomplete: "email" %>
</div>

<div class="field">
<%= f.label :image %><br />
<%= f.file_field :image %>
</div>

<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>

<div class="field">
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, autocomplete: "new-password" %>
<% if @minimum_password_length %>
<br />
<em><%= @minimum_password_length %> characters minimum</em>
<% end %>
</div>

<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>

<!-- -----------この部分を取り除きます--------------------------------------------- -->
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
<!-- -----------この部分を取り除きます--------------------------------------------- -->

<div class="actions">
<%= f.submit "Update" %>
</div>
<% end %>

<h3>Cancel my account</h3>

<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>

<%= link_to "Back", :back %>


これで完了です。このフォームではパスワードは変更出来なくなっているので、そっちも消していいと思います。


キーとシークレットキーの管理について(credentials.yml.enc)

今回はcredentials.yml.encを使って行きます。

細かいところはこちらへ↓

Rails5.2から追加された credentials.yml.enc のキホン-Qiita

$ EDITOR="vi" bin/rails credentials:edit

EDITORを指定してください。今回はvimです。

以下の記述を追加します。ご自身のkeyとserect_keyを記述してください。

facebook:

key: "App ID"
secret: "App Secret"
twitter:
key: "API key"
secret: "API secret"

credentials.yml.encファイル自体の中身が暗号化されてしまうため、参照するには以下のコマンドで入力する必要があります。

$ rails credentials:show

また、値は以下のコマンドで参照出来ます。(コンソールで試してみます)

irb(main):001:0> Rails.application.credentials.facebook

=> {:key=>App ID, :secret=>"App Secret"}
irb(main):002:0> Rails.application.credentials.facebook[:key]
=> App ID
irb(main):003:0>

上のコードを参考にconfig/initializers/devise.rbを書き換えます。


config/initializers/devise.rb


Devise.setup do |config|
config.omniauth :facebook, Rails.application.credentials.facebook[:key],
Rails.application.credentials.facebook[:secret]

config.omniauth :twitter, Rails.application.credentials.twitter[:key],
Rails.application.credentials.twitter[:secret]
end


これで完了です。問題なくログインできると思います。(ローカル環境)


Prodction環境の設定(heroku)

アクセス用のkeyとsecret_keyを渡してあげる必要があるります。

参考サイト(詳しくはこちら)↓

secrets.ymlや環境変数をRails 5.2のEncrypted Credentialsに移行する

$ heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

git push heroku masterを行う前に実行してください。

先にpushするとエラーが出てしまいました。


"OmniAuth" の脆弱性対策

githubにpushするとomniauthの脆弱性を発見しましたとメールがきました。

対策として以下を参考にしました。↓

[Rails]gem "OmniAuth" の脆弱性対策-Qiita


gemの追加


gemfile

gem "omniauth-rails_csrf_protection"



リンクにメソッドを宣言する

リンクにmethod: :postを加えるだけです。


devise/shared/_links.html.erb


# 上記のコード省略

<!-- --------この部分のコードです。------------------------------------------- -->
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br />
<% end %>
<% end %>
<!-- -------------ここまで----------------------------------------------------- -->



その他

twitterとfacebookのアカウントが1つしかなかったため、何度も消して試してました。

以下データベースのリセットのコマンドです。


development

$ rails db:migrate:reset


prodction(heroku)

$ heroku pg:reset DATABASE

このコマンドだともう一度heroku run rails db:migrateを行う必要があります。

多分もっといいコマンドがあるはず。


最後に

これでtwitterとfacebookでログインすることができます。

まだまだ、書いている途中なので、付け加えて行きます。