こんにちは、hachi8833です。Devise How-TO翻訳シリーズは、需要の多いOmniAuth関連を当面優先してお送りいたします。どうぞよろしくお願いします。
DeviseでOmniAuthを使うことで、Facebook認証、Twitter認証、Googleアカウント認証、GitHubアカウント認証などを導入できます。本翻訳記事の第一弾は、その基礎にあたる部分の解説です。
Devise Wikiもくじリンク
- 「ワークフローのカスタマイズ」
- 「認証方法のカスタマイズ」「OmniAuth」
- 「ビュー/コンテンツのカスタマイズ」「特権/認証」
- 「テスト」「特殊な設定」
- 「アプリでのその他の設定」「JavaScript」
- 「他の認証プラグインからの移行」「アップグレード」
概要
- 原文: OmniAuth: Overview
- リビジョン: 12 Feb · 171 revisions
読みやすさのため、config.omniauth
のコードを途中で改行しています。
原文の更新や誤りにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。
OmniAuth: 概要(翻訳)
Deviseはバージョン1.2からOmniAuthとの統合をサポートしています。このWikiページでは「とあるOAuthプロバイダ」を例に使って連携の基本を解説します。
Deviseバージョン1.5はOmniAuth 1.0以降をサポートするため、本チュートリアルではこれを用います。
始める前に
アプリケーションにはconfig.omniauthによってomniauth providerミドルウェアが追加されることを理解しておいてください。つまり、このproviderミドルウェアをconfig/initializers/omniauth.rbに再度追加してはいけないということです。もし再追加すると両方ともクラッシュし、認証が常に失敗してしまいます。
Facebookの例
最初にアプリにOmniAuth gemを追加します。この場合はGemfileに以下のように記述します。
gem 'omniauth-facebook'
ここではFacebookを例として使いますが、他のどんなOmniAuth gemを使ってもかまいません。gem名は一般に「omniauth-プロバイダ名」の形式になっています。プロバイダ名はfacebookだったりtwitterだったりします。完全なプロバイダリストについてはList of Strategiesをご覧ください。
次に、Userモデルにprovider
カラム(文字列)とuid
カラム(文字列)を追加します。
rails g migration AddOmniauthToUsers provider:string uid:string
rake db:migrate
次に、使うプロバイダをconfig/initializers/devise.rbで宣言する必要があります。
config.omniauth :facebook,
"APP_ID",
"APP_SECRET"
注意: v2.0.1にはコールバックURLエラーの問題があります。回避するには、configに
callback_url
を追加する必要があります。
config.omniauth :facebook,
"APP_ID",
"APP_SECRET",
callback_url: "CALLBACK_URL"
上のAPP_ID
とAPP_SECRET
は、アプリのidとsecret(秘密情報)に置き換えます。CALLBACK_URL
はhttps://myapp.com/users/auth/facebook/callback
のような形式にする必要があります。
パーミッションやリクエストされたスコープを変更するには、the omniauth-facebook gemのREADMEをご覧ください。
strategyの設定が終わったら、モデル(app/models/user.rbなど)に以下を記述してOmniAuthを有効にします。
devise :omniauthable, :omniauth_providers => [:facebook]
注意: Railsサーバーを実行している場合は、
Devise
イニシャライザに:omniauthable
を追記するなどして変更した後にRailsを再起動しないとエラーになります。
現時点では、Deviseでomniauthableにできるモデルは1つだけです。OmniAuthで複数のモデルを使いたい場合は、OmniAuth with multiple modelsをご覧ください。
User
モデルをomniauthable
にすると、config/routes.rbにdevise_for :users
を記述することで以下のURLメソッドがDeviseによって作成されます。
user_omniauth_authorize_path(provider)
user_omniauth_callback_path(provider)
Deviseは*_url
というメソッドは作成しません(そのディレクトリより上でコールバックヘルパーを使うことはありえないため)。
Facebookによる認証を提供するには、レイアウトに上の1つ目のメソッドを以下のように追加します。
<%= link_to "Sign in with Facebook", user_facebook_omniauth_authorize_path %>
注意:
user_omniauth_authorize_path
メソッドに渡されるシンボルは、Deviseのconfigブロックに渡されるプロバイダのシンボルと一致します。どのシンボルを渡せばよいかについては、omniauth-*
のREADMEをご覧ください。
上のリンクをクリックすると、ユーザーはFacebookにリダイレクトされます(リンクが表示されていない場合は、サーバーを再起動してください)。
Facebook側で認証情報が挿入されると、再度リダイレクトしてアプリケーションのコールバックメソッドに戻ります。コールバックを実装する場合に最初に必要なのは、config/routes.rbを開いて、Omniauthコールバックを実装するコールバックをDeviseに伝えることです。
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
次は、以下のコードのみを含むapp/controllers/users/omniauth_callbacks_controller.rb
を追加します。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
実装するコールバックのアクションは、プロバイダと同じ名前にする必要があります。facebookプロバイダのアクションの場合、たとえば以下のようなコードをコントローラに追加できます。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# 以下のメソッドをモデル(app/models/user.rbなど)で実装する必要がある
@user = User.from_omniauth(request.env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, :event => :authentication #@userが無効だと例外がスローされる
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path
end
end
このアクションについていくつか補足します。
- FacebookからOmniAuthで取得したすべての情報は
request.env["omniauth.auth"]
のハッシュでアクセスできます。取得できる情報の種類については、OmniAuthのドキュメントとと各gem(Facebookの場合はomniauth-facebook)のREADMEをご覧ください。 -
有効なユーザーが見つかると、Deviseの
sign_in
メソッドまたはsign_in_and_redirect
メソッドでサインインできます。:event => :authentication
はオプションであり、Wardenのコールバックを使う場合にのみ渡す必要があります。 -
Deviseのデフォルトのメッセージの中からflashメッセージに流用することも、使わないこともできます。
-
ユーザーがサイトから離れたら、OmniAuthのデータをセッションに保存します。データの保存では、
devise.
が名前空間のキーとして使われます。これにより、ユーザーがサインインに使ったセッションからdevise.
で始まるすべてのデータがDeviseによって削除され、セッションが自動的にクリーンアップされます。最後に、ユーザーを元の登録フォームページにリダイレクトします。
コントローラを定義したら、モデル(app/models/user.rbなど)で以下のようにfrom_omniauth
メソッドを実装します。
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.name = auth.info.name # Userモデルにnameカラムがあるとする
user.image = auth.info.image # Userモデルにimageカラムがあるとする
# Deviseのconfirmableを使い、かつプロバイダがメールのバリデーションを行っている場合は、
# 以下の行のコメントを解除して確認メールをスキップする
# user.skip_confirmation!
end
end
このメソッドは、provider
フィールドとuid
フィールドを指定して既存のユーザーを検索します。ユーザーが見つからない場合は、ランダムなパスワードなどの情報を追加して新しいユーザーを作成します。
なお、first_or_create
メソッドを使うと、ユーザー作成時にprovider
フィールドとuid
フィールドが自動的に作成されることにご注意ください。first_or_create!
メソッドも基本的に同じですが、ユーザーレコードのバリデーションに失敗するとExceptionがraiseされる点が異なります。
訳注:
first_or_create
メソッドやfirst_or_create!
メソッドは以下のようにActiveRecord::Querying
にdelegate
で実装されていますが、APIドキュメントはありません。
# https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/querying.rb#L5 より
module ActiveRecord
module Querying
...
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
また、DeviseのRegistrationsController
はリソースをビルドする前にデフォルトでUser.new_with_session
を呼び出します。つまり、ユーザー登録(サインアップ)前にユーザーが初期化された場合にセッションのデータをコピーする必要がある場合は、モデルでnew_with_session
のみを実装すればよいということです。
以下のコード例では、Facebookのメールが取得できる場合にメールをコピーします。
class User < ApplicationRecord
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end
end
最後に、ユーザーがFacebookでのユーザー登録(サインアップ)をキャンセルできるようにするには、ユーザーをcancel_user_registration_path
にリダイレクトするようにします。これにより、Deviseが開始したセッションデータはすべて削除され、上のnew_with_session
フックがその後呼ばれることはなくなります。
ログアウト用リンク
# config/routes.rb
devise_scope :user do
delete 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end
上のコードだけでできます。動作がうまく統合されるようになったら、以下を参考に統合テストを書きましょう。
OmniAuthだけで認証する
他の認証方法を使わずOmniAuthだけで認証する場合、new_user_session
という名前のルーティングを定義する必要があります(未定義の場合はrootが使われます)。
この場合のルーティング例を以下に示します。OmniAuthでデータベースや他の認証方法を併用している場合、以下は不要です。
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
devise_scope :user do
get 'sign_in', :to => 'devise/sessions#new', :as => :new_user_session
get 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
end
上のルーティングを使う場合、sessionsコントローラには何も処理を追加しません。たとえばプロバイダの認証リンクを表示できればよいのであれば、これで十分です。
注意: Deviseで
sign_out_via = :delete
を設定している場合は、sign_out
ルーティングがDELETE
リクエストにマッチするようコードの修正が必要になることがあります。
また、:database_authenticatable
を使っていない場合は、以下のようにnew_session_path(scope)
ヘルパーメソッドを定義して、失敗した場合に正しくリダイレクトする必要があります。
class ApplicationController < ActionController::Base
# ...
def new_session_path(scope)
new_user_session_path
end
end
トラブルシューティング
ユーザーがメールアドレスの利用を許可していなかった場合
Facebookの新しいログインダイアログでは、ユーザーはメールアドレスの入力を拒否できるようになっています。通常、Deviseの登録ではメールが必要になります。こういう場合の手っ取り早い解決方法は、メールアドレスがない場合は再度リクエストするというものです。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
if request.env["omniauth.auth"].info.email.blank?
redirect_to "/users/auth/facebook?auth_type=rerequest&scope=email"
end
end
end
Facebookから返されたメールアドレスが空になっている場合
2015年7月8日以降、FacebookはAPIをv2.4に更新しました。このため、email
フィールドにアクセスするにはinfo_fields
の追加が必要です。
config.omniauth :facebook,
"APP_ID",
"APP_SECRET",
scope: 'email',
info_fields: 'email,name'
OpenSSLエラー
以下のようなOpenSSLエラーが発生することがあります。
OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed):
この場合、CA証明書ファイルの保存場所をOmniAuthに対して明示的に指定する必要があります。これには、certified gemを使う方法と、以下の方法があります。後者は実行しているOS環境によって異なります。
config.omniauth :facebook,
"APP_ID",
"APP_SECRET",
:client_options => {:ssl => {:ca_path => '/etc/ssl/certs'}}
Herokuの場合、CAファイルは/usr/lib/ssl/certs/ca-certificates.crtに保存されます。
Engine Yard Cloud serversの場合、CAファイルは/etc/ssl/certs/ca-certificates.crtに保存されます。
証明書の設定には、:ca_file
キーを使います。
config.omniauth :facebook,
"APP_ID",
"APP_SECRET",
:client_options => {:ssl => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}}
OAuth gemを使うstrategy(omniauth-oauthなど)を利用する場合、次の方法で証明書ファイルを指定します。
config.omniauth :facebook,
"APP_ID",
"APP_SECRET",
:client_options => {:ca_file => '/usr/lib/ssl/certs/ca-certificates.crt'}
Mac OS Xでは証明書はファイルシステムではなくMacのキーチェーンアプリに保存されるので、Mac OS上での開発中に限れば、以下のように単に証明書の検証を無効にするのが最も簡単です。
require "omniauth-facebook"
config.omniauth :facebook,
"APP_ID",
"APP_SECRET",
:client_options => { :ssl => { :verify => !Rails.env.development? } }
このエラーの詳しい議論については、#260をご覧ください。
strategyクラスを読み込めない場合
Deviseが何らかの理由でアプリのstrategyクラスを読み込めない場合、以下のように:strategy_class
オプションで明示的に指定できます。
config.omniauth :facebook,
"APP_ID",
"APP_SECRET",
:strategy_class => OmniAuth::Strategies::Facebook
関連記事(Devise)
- [Devise How-To]ユーザー登録ページへのルーティングをカスタマイズする(翻訳)
- [Devise How-To] sign_inとsign_outのデフォルトルーティングを変更する(翻訳)
- [Devise How-To]ユーザーのパスワードを自動生成する(シンプルな登録方法)(翻訳)
- Rails4: 古いdeviseのパスワードを新しいdeviseで使う方法
- Rails 3.1.0.rc8にしようとしたらdeviseが違うバージョンのbcryptに依存していてアップデートできない
- [Rails 3] deviseで使うモデルにfind_by_で始まる名前のscopeを定義するとrake db:migrate:resetが通らない
- [Rails 3] 失敗しないmigrationを書こう
- [Rails 3] Appサーバが複数だとdevise_openid_authenticatableで認証できない