#はじめに
自分が実装しているときに割とハマったので書きました。
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
します。
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を設定します。
Devise.setup do |config|
config.omniauth :facebook, 'App ID', 'App Secret'
config.omniauth :twitter, 'API key', 'API secret'
end
私の場合ローカルで繋がったのが確認出来るまでは直接書いちゃってます。
注意としてここに書いたままで、 githubなどのリモートリポジトリーにあげないこと。
上げるときは、dotenv
、figaro
、credentials.yml.enc
などで管理してください。
###userモデルにメソッドを追加
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に伝える必要があります。
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 コントローラーを作成します。
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" %>
このコードが書いてあるログインとサインアップ画面に出てると思います。
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
実際に出現しているリンクが以下のリンクです。
<%- 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 %>
<!-- -------------ここまで----------------------------------------------------- -->
適当で申し訳ないですが、こんな感じでリンクが出ます。
ここまで実装しますと、twitterとfacebookでログインが出来るようになります。
#編集ページについて
deviseはデフォルトで編集時にパスワードが必要になります。
twitter、facebookでログインするとこの場合はパスワードをランダムで生成してログインしているため、パスワードを把握しておらず、編集が出来ないと思います。
そこで編集時にパスワードを入力をしないでいいように編集します。(ただし、パスワードの変更はそのページでは出来なくなります)
私もなんでこうなるのかとか曖昧ですのでこちらを読んでもらえると助かります。
参考リンク↓
github-devise-wiki How To: Allow users to edit their account without providing a password
###ルートの作成
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
###コントローラーの作成
上のルートに合わせてコントローラーを作成します。(生身はサイトのコピーです)
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
(変更時に入力するパスワードのフォーム)が残っているため取り除きます。
<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
を書き換えます。
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の追加
gem "omniauth-rails_csrf_protection"
###リンクにメソッドを宣言する
リンクにmethod: :postを加えるだけです。
# 上記のコード省略
<!-- --------この部分のコードです。------------------------------------------- -->
<%- 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でログインすることができます。
まだまだ、書いている途中なので、付け加えて行きます。