- 更新日: 2016年11月7日
- Devise
RailsでDeviseとOmniAuthによるTwitter/FacebookのOAuth認証、および通常フォーム認証を併用して実装
Rails アプリケーションにて、Facebook/Twitter の OAuth認証を実装します。Devise と OmniAuth を使います。まずは事前に、Devise を導入し、通常の認証・認可の機能を追加する。認証用のユーザーのモデルを User としました。以下参照。
Rails4 にて Devise でユーザー登録・ログイン認証・認可の機能を追加 | EasyRamble
また、通常フォームでのユーザー登録・サインインと Twitter/Facebook の OAuth 認証を併用する仕様にしたいと思います。仕様は以下のページで考えましたので、そちらを先に読んでおくと分かりやすいかもです。
Rails4 で Devise と OmniAuth を使い、通常フォームでのユーザー登録・サインインと OAuth 認証を併用する仕様を考えた | EasyRamble
— 環境 —
Rails 5.0.0.1
Devise 4.2.0
omniauth 1.3.1
omniauth-facebook 4.0.0
omniauth-twitter 1.2.1
【追記 2016/11/07】
Rails 5.0.0.1 + Devise 4.2.0 + OmniAuth 1.3.1 という現時点(2016/11/07)の最新バージョン環境で、Rails アプリケーションに Devise + OmniAuth によるユーザー認証の実装を試しましたところ、記事公開当初と同様の手順で実装できました。
【追記ここまで】
— 記事初回公開時の環境 —
Rails 4.0.1
Devise 3.2.2
OAuth 認証用の gem をインストール
Gemfile に以下を追加。
1 2 3 4 5 6 7 |
$ vi Gemfile gem 'devise' gem 'omniauth' gem 'omniauth-twitter' gem 'omniauth-facebook' |
bundle install。
1 2 3 |
$ bundle install |
OAuth プロバイダーの一覧はこちら。→ List of Strategies
User モデルに OmniAuth で利用するカラムを追加するマイグレーション
1 2 3 |
$ bundle exec rails generate migration AddOmniauthColumnsToUsers provider uid name |
db/migrate/***_add_omniauth_columns_to_users.rb
1 2 3 4 5 6 7 8 9 |
class AddOmniauthColumnsToUsers < ActiveRecord::Migration def change add_column :users, :uid, :string, null: false, default: "" add_column :users, :provider, :string, null: false, default: "" add_column :users, :name, :string add_index :users, [:uid, :provider], unique: true end end |
マイグレート。
1 2 3 |
$ bundle exec rake db:migrate |
データベース確認。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
mysql> desc users; +------------------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | email | varchar(255) | NO | UNI | | | | encrypted_password | varchar(255) | NO | | | | | reset_password_token | varchar(255) | YES | UNI | NULL | | | reset_password_sent_at | datetime | YES | | NULL | | | remember_created_at | datetime | YES | | NULL | | | sign_in_count | int(11) | NO | | 0 | | | current_sign_in_at | datetime | YES | | NULL | | | last_sign_in_at | datetime | YES | | NULL | | | current_sign_in_ip | varchar(255) | YES | | NULL | | | last_sign_in_ip | varchar(255) | YES | | NULL | | | created_at | datetime | YES | | NULL | | | updated_at | datetime | YES | | NULL | | | uid | varchar(255) | NO | MUL | | | | provider | varchar(255) | NO | | | | | name | varchar(255) | YES | | NULL | | +------------------------+--------------+------+-----+---------+----------------+ 16 rows in set (0.00 sec) |
uid, provider, name の3つのカラムが追加されました。uid と provider でユニークな複合インデックスを作成したため、uid の Key は MUL(値が重複可能)となっています。
config/initializers/devise.rb に OAuth 認証に必要なキー・シークレットトークンを記述
Facebook, Twitter の developer サイトに登録して、OAuth 認証の API 利用に必要な key, secret を取得します。
Facebook の API Key
Home – Facebook開発者 で取得。
ログイン → 右上の「Register Now」から developer 登録。
Facebook Developers の右上の「+新しいアプリを作成」。
・アプリ名:任意
・名前空間:任意
・カテゴリ:その他(任意)
と入力して、App ID, App Secret を取得。
アプリをFacebookに結合する方法を選択で、Facebookでログインが可能なウェブサイトに「http://localhost:3000」を入力。
Twitter の API Key
Twitter Developers で取得。
ログイン → 右上 My Applications → Create a new application
・Name:任意
・Description:任意
・Website:適当なURL(http://localhost:3000 だとNG)
・Callback URL:http://127.0.0.1:3000/auth/twitter/callback
(http://localhost:3000 だとNG)
その後、Consumer key, Consumer secret を取得し、 key, secret を config/initializers/devise.rb に設定。
Settings で 「Allow this application to be used to Sign in with Twitter」にチェック。一回TwitterでOAuth認証を行うと、次回からは確認画面が省略される。
OmniauthでTwitter認証(OAuth認証) – yamotonalds’s blog
production 用と development 用でAPI Keyを使い分ける場合は、それぞれFacebook用/Twitter用に2つずつ取得します。取得した API Key を config/initializers/devise.rb に記述。Rails.env.production? で判定して使い分けしています。
config/initializers/devise.rb
1 2 3 4 5 6 7 8 9 10 11 12 |
Devise.setup do |config| # ... # API key if Rails.env.production? config.omniauth :facebook, "App ID", "App Secret" config.omniauth :twitter, "Consumer key", "Consumer secret" else config.omniauth :facebook, "App ID", "App Secret" config.omniauth :twitter, "Consumer key", "Consumer secret" end end |
User モデルに omniauthable を追加
使う Devise のモジュールを User モデルで設定します。
app/models/user.rb
1 2 3 4 5 6 7 8 |
class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :omniauthable, :recoverable, :registerable, :rememberable, :trackable, :validatable # ・・・ end |
私は、以上のように設定しました。今回は、:confirmable, :lockable, :timeoutable は使いません。
Devise は以下の URL ヘルパーメソッドを生成します。
user_omniauth_authorize_path(provider)
user_omniauth_callback_path(provider)
callback のヘルパーメソッドは直接使うことはありません。1番目の OAuth 認証用の URL ヘルパーメソッドをビューで使用します。
1 |
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %> |
OAuth の callback 用ルーティングを追加
config/routes.rb に callback 用のルーティングを追加します。
config/routes.rb
1 2 3 4 5 6 |
devise_for :users, :controllers => { :sessions => "users/sessions", :registrations => "users/registrations", :passwords => "users/passwords", :omniauth_callbacks => "users/omniauth_callbacks" } |
Users::OmniauthCallbacksController を作成
callback は provider と同名のメソッドを実装する必要があります。
app/controllers/users/
omniauth_callbacks_controller.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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.find_for_facebook_oauth(request.env["omniauth.auth"], current_user) if @user.persisted? set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format? sign_in_and_redirect @user, :event => :authentication else session["devise.facebook_data"] = request.env["omniauth.auth"] redirect_to new_user_registration_url end end def twitter # You need to implement the method below in your model @user = User.find_for_twitter_oauth(request.env["omniauth.auth"], current_user) if @user.persisted? set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format? sign_in_and_redirect @user, :event => :authentication else session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra") redirect_to new_user_registration_url end end end |
このアクションには3つの特徴がある。
1. OAuth 認証による provider からのレスポンスの情報は、request.env[“omniauth.auth”] にハッシュとして格納されている。
2. モデルから正当なユーザーが見つかった場合は、サインインさせなければならない。Devise のデフォルトのフラッシュメッセージは変更できます。次に、ユーザーをサインインさせリダイレクトする。sign_in_and_redirect メソッドに :event => :authentication 引数を渡し、全ての認証コールバックが呼ばれるようにしている。
3. ユーザーがログインを持続していない場合、セッションに OmniAuth 情報を格納する。session[“devise.facebook_data”] と devise. の名前空間でセッション情報を保存する。こうしておくと、ユーザーがサインインした時はいつでも、Devise が devise. で始まるセッション情報を削除し、セッションのクリーンアップを自動で行ってくれる。最後に、登録フォームにリダイレクトする。
なお、Twitter のコールバックのほうが、request.env[“omniauth.auth”].except(“extra”) となっているのは、ActionDispatch::Cookies::CookieOverflow エラー | EasyRamble の理由によるものです。また、User.find_for_facebook_oauth, User.find_for_twitter_oauth は後で実装する。
以降、OAuth と通常サインアップを併用させる仕様に基づく実装を行います。仕様の詳細はこちら。→ Rails4 で Devise と OmniAuth を使い、通常フォームでのユーザー登録・サインインと OAuth 認証を併用する仕様を考えた | EasyRamble
通常サインアップ時の uid フィールド設定の実装
※以下は参考非推奨です。
上記項目の実装は、Rails の認証プラグイン Devise での Strong Parameters について | EasyRamble に書いた、追加のパラメータを許可する方法で実装できます。ユーザー登録フォームに uid の hidden field を含ませます。uid を strong parameters の機能で許可し、また uid に対するモデルでの validation も追加します。
※参考非推奨ここまで。
【追記:2013/12/14】
当初、上記打ち消し線の実装(参考非推奨)を考えたのだけどやはり、フォームを由来せずに Devise::RegistrationsController の create アクション時に、uid を設定したほうが良いです。Devise のソースを読んで調べていたら Devise::RegistrationsController の build_resource(hash=nil) アクションをオーバーライドする方法を見つけました。ということで、それを継承した Users::RegistrationsController < Devise::RegistrationsController で build_resource(hash=nil) をオーバーライドして実装する方法で進めます。
How To: Override build_resource({})
Devise > Custom fields during registration – Google グループ
まずは、Users::RegistrationsController で build_resource(hash=nil) をオーバーライドします。resource を組み立てる前に、hash に hash[:uid] を追加しています。
app/controllers/users/registrations_controller.rb
1 2 3 4 5 6 7 8 |
class Users::RegistrationsController < Devise::RegistrationsController def build_resource(hash=nil) hash[:uid] = User.create_unique_string super end end |
続いて、User.create_unique_string を User モデルに定義。
app/models/user.rb
1 2 3 4 5 6 7 8 9 10 |
class User < ActiveRecord::Base # ・・・ # 通常サインアップ時のuid用、Twitter OAuth認証時のemail用にuuidな文字列を生成 def self.create_unique_string SecureRandom.uuid end # ・・・ end |
以上で、Devise::RegistrationsController の create アクション時に、独自に User モデルの uid 属性を、DB の users テーブルに保存することができます。
OAuth 認証時の email フィールド設定の実装
Twitter の OAuth 認証では Email アドレスが取得できないことに注意。また、通常フォームによるサインアップと OAuth 認証で Email が重複するのも NG です。
・FacebookでのOAuth認証の場合に取得したEmailアドレスが、通常フォームからのユーザー登録によりすでにレコードに存在している場合は、FacebookでのOAuth認証を許可しない。
・逆に、通常フォームからのサインアップ時のEmailアドレスが、FacebookでのOAuth認証によりすでにレコードに存在している場合は、通常フォームからのサインアップを許可しない。
上記項目を実装するには、OmniAuth: Overview の公式Wikiを参考にして、Userモデル(app/models/user.rb)で User.find_for_facebook_oauth クラスメソッドを定義する。これらのメソッドは、上の方の Users::OmniauthCallbacksController で定義した twitter, facebook のコールバックメソッドで使われるものです。
Emailの重複があった場合は、email カラムのユニークキーによる制約と Devise により面倒を見てくれるみたいです。自分の Twitter/Facebook アカウントで試してみました。まあ email がユニークな認証用キーになるので当たり前でしょうけど。
あとこの実装の場合、email カラムの取り扱い時には注意が必要。ランダム生成するTwitter OAuth認証時のEmailはでたらめなので、email カラム出力専用のヘルパーを作る。
Twitter で OAuth 認証したユーザーが、その後アカウント情報を正しい自分のEmailに変更した場合など、もう少し考えなければならないことがある。これは、最初にランダム生成するEmailアドレスの@部分以降を 〜@example.com にしておいて、example.com じゃないドメインになっていたら、ユーザーが自分のEmailアドレスに変更済みなどとチェックできる。(example.com は例示で使われる予約ドメイン。)
User.find_for_facebook_oauth, User.find_for_twitter_oauth の定義を含みつつ、User モデルの全体は以下のようになりました。
app/models/user.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :omniauthable, :recoverable, :registerable, :rememberable, :trackable, :validatable def self.find_for_facebook_oauth(auth, signed_in_resource=nil) user = User.where(:provider => auth.provider, :uid => auth.uid).first unless user user = User.create(name: auth.extra.raw_info.name, provider: auth.provider, uid: auth.uid, email: auth.info.email, password: Devise.friendly_token[0,20] ) end user end def self.find_for_twitter_oauth(auth, signed_in_resource=nil) user = User.where(:provider => auth.provider, :uid => auth.uid).first unless user user = User.create(name: auth.info.nickname, provider: auth.provider, uid: auth.uid, email: User.create_unique_email, password: Devise.friendly_token[0,20] ) end user end # 通常サインアップ時のuid用、Twitter OAuth認証時のemail用にuuidな文字列を生成 def self.create_unique_string SecureRandom.uuid end # twitterではemailを取得できないので、適当に一意のemailを生成 def self.create_unique_email User.create_unique_string + "@example.com" end end |
User.find_for_facebook_oauth, User.find_for_twitter_oauth は、ユーザーを DB から見つけるか、または見つからない場合にユーザーオブジェクトを生成して DB に保存するために実装します。
unless 節で新たなユーザーのOAuth認証の場合に、OAuth認証によって取得した name, provider, uid を設定しています。Facebook の場合は email も取得。Twitter の OAuth 認証時の email は、User.create_unique_email で一意になるように設定します。また password 属性が空ですと、encrypted_password が NULL になり弾かれるので、これも適当に設定しています。
Users::OmniauthCallbacksController のコールバックメソッドも、User モデルの self.find_for_provider_oauth メソッドも重複箇所が多いので、OAuth 認証で使う provider が多い場合などはリファクタリングが必要。provider の名前からメソッドを動的に生成すればコードが随分短くなりそうなので、いずれ挑戦してみたいと思います。
User.new_with_session について
OmniAuth: Overview を参照すると、サインアップ前に User モデルに情報を保存する場合、User.new_with_session を実装する方法もあるようです。
Devise の RegistrationsController はデフォルトで、リソース(Userモデルのインスタンスなど)を作成する前に、User.new_with_session クラスメソッドを呼び出します。これは次のことを意味する。もし、ユーザーがサインアップ前に初期化される時にセッションなどから情報をコピーする必要がある場合、モデルで new_with_session を実装する必要がある。
公式Wikiの参考コードは以下。
1 2 3 4 5 6 7 8 9 |
class User < ActiveRecord::Base 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 |
なお、最初はこの User.new_with_session(params, session) を利用してして、通常サインアップ時の uid 設定を実現しようとしたのですが上手くできませんでした。
new_with_session という名前なので、セッションスタートと同時に user を作るのかな?そうだとすると、当然その前の uid に対する strong parameters ?による制約で、サインアップがはじかれるという状態になってしまうのが理由でしょうか。どこかの provider で OAuth 認証する場合であれば上手く動くはず。ということで、今のところこの new_with_session は使っていません。
ビューに OAuth 認証用のログインへのリンクを表示
最後に、ビューで Twitter/Facebook への OAuth 認証用のリンク先URLを表示させます。
1 2 |
<%= link_to "Sign in wiht Facebook", user_omniauth_authorize_path(:facebook) %> <%= link_to "Sign in with Twitter", user_omniauth_authorize_path(:twitter) %> |
私は以下のように、app/views/layouts/application.html.erb のヘッダーのナビゲーション部分に追加しました。
app/views/layouts/application.html.erb
1 2 |
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook), :class => 'navbar-link' %> <%= link_to "Sign in with Twitter", user_omniauth_authorize_path(:twitter), :class => 'navbar-link' %> |
以上で終了、さっとブラウザで試した感じでは上手く動作している模様。色々実装を先に試しちゃったのでテスト書かなきゃです…
- – 参考リンク –
- Welcome to the Devise Wiki!
- How Tos
- OmniAuth: Overview
- Rails – deviseでfacebook,twitter認証 – Qiita [キータ]
- Devise を導入して Facebook 認証との統合をやってみる | yukku++
- Devise の関連記事
- RailsのDevise認証機能での実装チェックリストまとめ
- Deviseで送信されるメールのfrom(送信者メールアドレス)を変更
- Facebook の OAuth 認証で OAuthException(191)エラー
- Rails Devise でパスワードリセットなどのメールテンプレート(Mailer ビュー)をカスタマイズ
- Rails + Devise 環境でのフレンドリーフォワーディング機能を修正
- Deviseでユーザー登録完了時にウェルカムメールを送信する
- Rails Devise でユーザーがプロフィール情報を更新後に元のページにリダイレクトさせる
- Devise でユーザーがパスワードなしでアカウント情報を変更するのを許可
- Rails Deviseの日本語化辞書ファイル(devise.ja.yml)
- Rails + Devise で admin ユーザー(管理者)を削除できないようにする
- 初回公開日: 2013年12月13日
Leave Your Message!