LoginSignup
23
22

既存のDevise認証アプリに OmniAuth認証を追加する

Last updated at Posted at 2015-07-31

Screenshot 2015-08-01 11.34.30.png

OmniAuth認証についてのメモ。

概要

  • Deviseで認証を行うRailsアプリに対して、OmniAuthでの認証機能を追加する。
  • Twitterをプロバイダーとする。
  • OmniAuthで認証する場合は、パスワード入力を免除する。
  • 新規登録時、emailは必須項目とする。
  • Deviseのconfirmableモジュールでemailの確認をする。

手順

##認証してもらいたいプロバイダーにアプリを登録し、APIキー等を取得

TwitterのApplication Managementサイトに行き、フォームに必要事項を記入して提出

Screenshot 2015-07-31 10.42.18.png

  • Name: アプリ名(ユーザーが認証手続きするときに表示される)
  • Description: アプリについての説明 (ユーザーが認証手続きするときに表示される)
  • Website: 今Websiteがなければ、仮のURLを記入する。後に変更可能。
  • Callback URL: omniauth-twitterにより認証完了後に呼ばれるURLを記入する。Deviseを使用している場合はDeviseが面倒を見てくれるので、深く考えずhttp://127.0.0.1:3000/auth/twitter/callbackを入力する。

左から3番目のタブ(Keys and Access Tokens)をクリックし、API KeyとAPI Secretを確認。

Screenshot 2015-07-31 13.33.45.png

Gemfileにomniauth-twitterを追加

/Gemfile
#...

gem 'omniauth-twitter'

#...
$ bin/bundle

##API KeyとAPI Secretを/config/secrets.ymlに加える

直接ペーストせずに環境変数経由で渡すようにするとよい。
これで以降は、Development/Producton環境を問わずRails.application.secrets.twitter_api_keyRails.application.secrets.twitter_api_secretとしてAPI KeyAPI Secretにアクセス可能になる。

/config/secrets.yml
production:
  # ==> Rails
  secret_key_base:     <%= ENV["SECRET_KEY_BASE"] %>
  # ==> OmniAuth
  facebook_api_key:    <%= ENV["FACEBOOK_API_KEY"] %>
  facebook_api_secret: <%= ENV["FACEBOOK_API_SECRET"] %>
  twitter_api_key:     <%= ENV["TWITTER_API_KEY"] %>
  twitter_api_secret:  <%= ENV["TWITTER_API_SECRET"] %>

development:
  # ==> Rails
  secret_key_base:     <%= ENV["SECRET_KEY_BASE_DEV"] %>
  # ==> OmniAuth
  facebook_api_key:    <%= ENV["FACEBOOK_API_KEY"] %>
  facebook_api_secret: <%= ENV["FACEBOOK_API_SECRET"] %>
  twitter_api_key:     <%= ENV["TWITTER_API_KEY"] %>
  twitter_api_secret:  <%= ENV["TWITTER_API_SECRET"] %>

test:
  # ==> Rails
  secret_key_base:     <%= ENV["SECRET_KEY_BASE_TEST"] %>

Development環境の場合、dotenvを利用して、API Key等を/.envから自動で環境変数に割り当てることも可能。

/.env
#...

# ==> OmniAuth
TWITTER_API_KEY    = "T0ONiGrNuMNsKb5AyNC05mOpe"
TWITTER_API_SECRET = "FxrQ6ddvlVbIaUlvEm0xg4fgoK9ACmWWnkesdU60ck1vJFoBM8"

#...

この時点でgitに秘密のデータがaddされないことを確認しておくのが賢明。

.gitignore
# Ignore secrets
.env
.secret

Deviseにプロバイダー名、API Key、API Secretを渡す

/config/initializers/devise.rbの230行目あたりにプレースホルダーがある。

/config/initializers/devise.rb
#...

  # ==> OmniAuth
  config.omniauth :twitter, Rails.application.secrets.twitter_api_key,
                            Rails.application.secrets.twitter_api_secret

#...

注意: 複数の参考資料を参照していると混乱しやすいが、Devise認証付きのアプリでOmniAuthを実装する場合は、Deviseがmiddlewareを作ってくれるので、/config/initializers/omniauth.rbは不要。ここで手動でmiddlewareを作ってしまうと、Deviseのつくったmiddlewareと衝突し、認証が全て失敗してしまう。

/config/initializers/omniauth.rb
# 注意: Devise付きのアプリでOmniAuthを実装する場合は不要
# Rails.application.config.middleware.use OmniAuth::Builder do
#   provider :twitter, Rails.application.secrets.twitter_api_key,
#                      Rails.application.secrets.twitter_api_secret
# end

##omniauthableモジュールをUserモデルに追加

/app/models/user.rb
class User < ActiveRecord::Base
  #...

  devise :database_authenticatable, :registerable, :confirmable,
         :recoverable, :rememberable, :trackable, :validatable,
         :omniauthable, omniauth_providers: [:twitter]

  #...

これでDeviseが自動でSign in with Twitterリンクを生成してくれる。試しに/users/sign_upに行ってリンクがあるかどうか確認する。

Screenshot 2015-08-01 11.37.19.png

user_omniauth_authorize_path(provider)

Deviseが自動で 生成したuser_omniauth_authorize_pathメソッドを利用して好きなところにOmniAuth認証へのリンクを貼る。

= link_to "Log in with twitter", user_omniauth_authorize_path(:twitter)

OmniAuth認証データをデータベースに保存するためのマイグレーション

$ rails g migration add_omniauth_to_users provider uid
  • provider: 認証したプロバイダー名
  • uid: 一意的ID
$ rake db:migrate

##OmniAuth callbackを取り扱うコントローラを作成

$ rails g controller omniauth_callbacks

本コントローラをdevise_forに登録する

/config/routes.rb
Rails.application.routes.draw do

  #...

  devise_for :users, controllers: { omniauth_callbacks: 'omniauth_callbacks' }

  #...

end

認証失敗時の取り扱い等の機能を取り込むため、super classはDevise::OmniauthCallbacksControllerとする。
複数のプロバイダーを利用する場合も内容は同じなので、alias_methodを用い、同じコードを複数のプロバイダーに対して使用できるようにする。

/app/controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def all
    # 認証データを元にデータベースでユーザーを探し、なければ作る。
    user = User.from_omniauth(request.env["omniauth.auth"])
    if user.persisted?  # ユーザーがデータベース上に存在している。
      sign_in_and_redirect user  # ユーザーをsign_inする
      set_flash_message(:notice, :success, kind: __callee__.to_s.capitalize) if is_navigational_format?
    else  # 何らかの理由でデータベースに保存されていない。
      session["devise.user_attributes"] = user.attributes  # 認証データを覚えておく。
      redirect_to new_user_registration_url(from_omniauth_callback: "1")  # ユーザーを新規登録ページに転送。
    end
  end

  alias_method :twitter, :all
end

UserモデルにOmniAuth認証処理に使用するメソッドを追加

/app/models/user.rb
class User < ActiveRecord::Base

  #...

  devise :database_authenticatable, :registerable, :confirmable,
         :recoverable, :rememberable, :trackable, :validatable, :omniauthable

  # OmniAuth認証データを元にデータベースでユーザーを探す。なければ新しく作る。
  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.username = auth.info.nickname
      # パスワード不要なので、パスワードには触らない。
    end
  end

  # session["devise.user_attributes"]が存在する場合、それとparamsを組み合わせてUser.newできるよう、Deviseの実装をOverrideする。
  def self.new_with_session(params, session)
    if session["devise.user_attributes"]
      new(session["devise.user_attributes"]) do |user|
        user.attributes = params
        user.valid?
      end
    else
      super
    end
  end

  # ログイン時、OmniAuthで認証したユーザーのパスワード入力免除するため、Deviseの実装をOverrideする。
  def password_required?
    super && provider.blank?  # provider属性に値があればパスワード入力免除
  end

  # Edit時、OmniAuthで認証したユーザーのパスワード入力免除するため、Deviseの実装をOverrideする。
  def update_with_password(params, *options)
    if encrypted_password.blank?            # encrypted_password属性が空の場合
      update_attributes(params, *options)   # パスワード入力なしにデータ更新
    else
      super
    end
  end
end

新規登録ページでパスワード欄が不要な場合は隠す

例、OmniAuth認証後にemailのみ入力を求める場合。

/app/views/devise/registrations/new.html.haml
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|

  - if params[:from_omniauth_callback]
    .alert.alert-danger Email address is required.
  - else
    = devise_error_messages!

  .field.form-group
    = f.text_field :username, autofocus: false, class: 'form-control', placeholder: "Username"

  .field.form-group
    = f.email_field :email, autofocus: false, class: 'form-control', placeholder: "Email"

  - if f.object.password_required?

    .field.form-group
      = f.password_field :password, autocomplete: "off", class: 'form-control',
        placeholder: "Password (#{@minimum_password_length} characters min)"

    .field.form-group
      = f.password_field :password_confirmation, autocomplete: "off", class: 'form-control',
        placeholder: "Password confirmation"

  .actions
    = f.button "Create my account", class: "submit btn btn-success btn-lg btn-block",
      data: { disable_with: "<i class='fa fa-spinner fa-spin'></i> Processing..." }

参考文献

23
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
22