OmniAuthを使ってfacebookログインする

2014-07-18T00:00:00+00:00 rspec Ruby Rails

公式: https://github.com/intridea/omniauth

タイトル通りRails(じゃなくても出来る。Sinatraとかもサポートされている)で、facebookへのログインを行ってアカウント情報なりを利用するようなパターン(ソーシャルログインなり登録時の情報の自動設定なり)を行う場合には、omniauthでサポートされているfacebook strategyを使えば簡単にできちゃう模様

facebook APIの設定

https://developers.facebook.com からアプリケーションを作成しアプリケーションIDとシークレットキーをコピーする。でValid OAuth Redirect URIsっていう所に認証後にリダイレクト先を指定しておかないとOAuth failureするので設定しておく

とりあえずはfacebook側の設定はこんだけ

Gemfile

source "https://rubygems.org"

gem "omniauth-facebook"

# テストで使うので
gem "rspec-rails", group: :test
gem "capybara-rails", group: :test

まぁ設定したらbundle install

config/initializers/omniauth.rb

ドキュメント(README.md)通りにconfig/initializers/omniauth.rbを作る

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook,
    "[APP ID]",
    "[APP SECRET KEY]",
    # facebook appで設定したリダイレクト先と合致するように設定
    :callback_path => "/auth/facebook/callback"

  on_failure do |env|
    # 認証をキャンセルしたりだとかした場合等に/auth/failureにぶっ飛ばす
    OmniAuth::FailureEndpoint.new(env).redirect_to_failure
  end
end

omniauth-facebookに関しての設定項目は https://github.com/mkdynamic/omniauth-facebook を参照

config/routes.rb

auth_facebook_callback GET    /auth/facebook/callback(.:format) sessions#callback
          auth_failure GET    /auth/failure(.:format)           sessions#failure
              accounts GET    /accounts(.:format)               accounts#index

っていうようなルーティングを作るので

Rails.application.routes.draw do
  get "/auth/facebook/callback" => "sessions#callback"
  get "/auth/failure" => "sessions#failure"

  resources :accounts
end

ってな感じでSessionsControllerとAccountsControllerが必要なので(ry

app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def callback
    session[:auth] = auth_hash
    redirect_to :controller => :accounts
  end

  def failure 
    render :status => 500, :text => "error"
  end

  private
    def  auth_hash
      request.env["omniauth.auth"]
    end

end

んな感じで

app/controllers/accounts_controller.rb

class AccountsController < ApplicationController
  def index
    auth = session[:auth]
    return redirect_to "/auth/failure" if auth.nil?

    # :authがOmniAuthのAuthHashを検証するような場合
    # 詳しくはhttps://github.com/intridea/omniauth/blob/master/lib/omniauth/auth_hash.rb#L19
    # OmniAuth::AuthHash.new(auth).valid?

    @user = auth
  end
end

な感じでapp/views/accounts/index.erbで

name = <%= @user["info"]["nickname"] %>

な感じでnicknameを出力しているだけ。

まぁ要は/auth/facebookにアクセスしアクセス許可を承認されると /auth/facebook/callbackに行って、セッションデータにログイン時のユーザー情報を格納後/accountsにリダイレクト、そこで最終的に情報が表示されるような感じ

spec/rails_helper.rb

RSpec3(+Rails)辺りからはspec/rails_helper.rbにRailsに特有される設定等を行う模様げっぽいので

# ここから追加
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({
  :provider => "facebook",
  :info => {
    :nickname => "hoge"
  }
})

require "capybara/rails"
require "capybara/rspec"
# ここまで

RSpec.configure do |config|

  # 追加
  config.include Capybara::DSL

end

な感じでOmniAuth.config.test_modeを使用する事でテスト時には/auth/facebook等にリクエストした場合は問答無用でコールバック先にリダイレクトされる模様。でその際のomniauth.authなのはOmniAuth.config.mock_authで設定する事が可能。んまぁこういうところはテストを実行する前にbefore :each等で設定するべきなんじゃないのかなって思う

spec/requests/auth_spec.rb

require "rails_helper"

describe "oauth callback test", :js => false do

  before :each do
    page.driver.options[:follow_redirects] = true
  end

  it "/auth/facebookにリクエストした場合 (リダイレクトをフォローしない場合" do
    page.driver.options[:follow_redirects] = false
    visit "/auth/facebook"

    # /auth/facebookにアクセスするとOmniAuth.config.test_modeによりcallbackへ飛ばされる
    expect(page.status_code).to be(302)

    # /auth/facebookから飛ばされた先が/auth/facebook/calbackかをテスト
    expect(page.response_headers["Location"]).to match(%r!/auth/facebook/callback$!)
  end

  it "/auth/facebookにリクエストした場合 (リダイレクトをフォローする場合" do
    visit "/auth/facebook"

    # /auth/facebook -> /auth/facebook/callback -> /accounts にぶっ飛ぶ
    expect(page.status_code).to be(200)

    # OmniAuth.config.mock_authで指定しているデータにより、{ info => { nickname => "hoge" }}が作用してレンダリングされているか検証
    expect(page).to have_content("name = hoge")
  end

  it "/auth/facebook/callbackに直接リクエストした場合" do
    visit "/auth/facebook/callback"

    # 一個上のテストと同様
    expect(page.status_code).to be(200)
    expect(page).to have_content("name = hoge")
  end

  it "OmniAuth.config.mock_authに:invalid_credentialsを指定した場合" do
    page.driver.options[:follow_redirects] = false
    OmniAuth.config.mock_auth[:facebook] = :invalid_credentials

    visit "/auth/facebook/callback"

    # :invalid_credentialsをした場合にon_failureで指定している/auth/failureへぶっ飛ぶ事を検証
    expect(page.status_code).to be(302)
    expect(page.response_headers["Location"]).to match(%r!/auth/failure?.*!)
  end
end

終わり。っていう事で若干適当感がハンパないけどomniauthとomniauth strategyを使えば簡単にソーシャルログイン等を使ってデータを取得したり出来るんじゃないかと

余談としてomniauthで使えるstrategyは https://github.com/intridea/omniauth/wiki/List-of-Strategies に載ってるので使いたいサービスとかに対応するのを使えば良いっぽい

追記1: omniauthのURL prefixを変える方法

参考: http://stackoverflow.com/questions/10033413/how-to-change-route-of-omniauth-from-auth-provider-to-myapp-auth-provider

デフォルトだと/authになっていて、それがStrategyを含むと/auth/:providerっていう感じになるわけなんだけど、それのprefixを変えたい場合

Rails.application.config.middleware.use OmniAuth::Builder do
  configure do |config|
    config.path_prefix = "/user/auth"
  end

  # 以降省略
end

ってな感じにすれば良いらしい

rails g migration JAX-RSをやってみる (15) - ForcedAutoDiscoverable -