DoorkeeperとDeviseでOAuth2によるログイン機能を作る

More than 1 year has passed since last update.


始めに

2つのウェブサービスを運営しているとして、「片方のウェブサービスのアカウント(IDとパスワード)でもう片方のウェブサービスにログインしたい!」という、いわゆる「Facebookアカウントでログイン」的な物を、OAuth2による認証機能を提供してくれるGem「Doorkeeper」を用いて実装するまでのメモ書きです。


環境


  • Rails 4.2.3

  • Ruby 2.2.2

  • Vagrantによる仮想環境


Doorkeeper

https://github.com/doorkeeper-gem/doorkeeper

OAuth2による認証機能をアプリに追加するGemです。これを使うことでアプリにFacebookAPIのような機能(ログインなどなど)を追加することが出来ます。


Devise

https://github.com/plataformatec/devise

認証機能を追加する定番のGemです。大抵、必要な機能が増えてきて魔改造される運命にあります。


provider側アプリの実装

RailsでAPIでのOAuth2認証をdoorkeeper + deviseで実装する

http://qiita.com/arakaji/items/39818b207f9c1c3c4058

provider側のアプリは基本的にこの記事の内容に沿って進めていきます。


必要なGem


Gemfile

gem 'devise'

gem 'doorkeeper'
gem 'omniauth'
gem 'oauth2'

$ bundle install


deviseの準備

$ bundle exec rails g devise:install

$ bundle exec rails g devise user
$ bundle exec rake db:migrate

userの部分はお好きなモデル名をどうぞ。


doorkeeperの準備

ここからが本番です。まずは下準備から。

$ bundle exec rails g doorkeeper:install

$ bundle exec rails g doorkeeper:migration
$ bundle exec rake db:migrate

以上を実行することで、doorkeeperに必要なファイルとDBが作られます。次にdoorkeeperでの認証ロジックにdeviseを用いるため、initializers/doorkeeper.rbを以下のように編集します。


initializers/doorkeeper.rb

resource_owner_authenticator do

current_user || warden.authenticate!(scope: :user)
end

userの部分はdeviseで作ったモデル名になります。

この後、アクセストークン取得のための設定を参考記事では行いますが、今回は認証コードを用いてトークンを取得するので、ここの設定は必要ありません。


doorkeeperの使い方


アプリケーション登録

doorkeeperではFacebookのAPIのように認証を利用するアプリケーションを登録し、それぞれに対してApplication_id, Secret_idが発行され、Callback_urlを設定します。

bundle exec rails sでRailsサーバーを立ち上げて、/oauth/applicationsにアクセスすると、登録されているアプリケーションの一覧が表示されます。そのページでNew Applicationボタンを押すとアプリケーションを登録するフォームが表示されるので、アプリケーションの名前とコールバックURLを入力すると、アプリケーションが登録されます。ここで入力するコールバックURLはclient側で設定したURLです。この記事の通りに設定した場合のURLはhttp://clientのvagrantのip:port/users/oauth/doorkeeper/callbackとなります。

Doorkeeper 2015-07-13 18-34-26.png


APIの作成

認証した後、client側アプリが使うためのAPIを作ります。今回はログイン処理なので、providerが持つユーザーデータを引き渡す処理を書きます。


app/controllers/api/api_controller.rb

module Api

class ApiController < ::ApplicationController
# ApplicationControllerでauthenticate_userを呼び出している場合、
# ここでも処理がはしり、401エラーの元なので、切っておく
before_action :authenticate_user!, only: []

private

def current_resource_owner
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end
end
end



app/controllers/api/credentials_controller.rb

module Api

class CredentialsController < ApiController
# loginメソッドを外部から呼び出す際にdoorkeeperで認証処理する
before_action :doorkeeper_authorize!

# ユーザーのデータをjson形式で送る
def login
render json: { user: current_resource_owner }
end
end
end



client側アプリの実装


必要なGem


Gemfile

gem 'devise'

gem 'omniauth'
gem 'omniauth-oauth2'
gem 'oauth2'

$ bundle install


deviseの準備

$ bundle exec rails g devise:install

$ bundle exec rails g devise user
$ bundle exec rake db:migrate

いつも通り。


oauthの処理を実装する

流れは


  1. deviseで作ったモデルにoauthを対応させる準備をする

  2. オレオレアプリ用のoauthのstrategyを作成する

  3. コールバック処理を書く

こんな感じ。


deviseで作ったモデルにoauthを対応させる準備をする

ざっくり言うとfacebookログインに対応させるときと同じ処理です。

まず始めに、deviseで作ったモデルに:omniauthableを追加します。


user.rb

class User < ActiveRecord::Base

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

これにより、URLヘルパーの

user_omniauth_authorize_path(provider)

user_omniauth_callback_path(provider)

が利用可能になり、ビューでuser_omniauth_authorize_path(provider)を呼び出すことでOAuthを使ったログイン処理が走ります。

次に、oauthでは認証時にuid(provider毎に一意に決まるID)とprovider(oauth認証を提供するアプリ名)を使うので、それを保存するカラムをDBに作成します。

$ rails g migration AddUidToUser


db/migrate/add_uid_to_user.rb

class AddUidToUser < ActiveRecord::Migration

def change
add_column :users, :uid, :string
add_column :users, :provider, :string
end
end

$ rake db:migrate


オレオレアプリ用のoauthのstrategyを作成する

oauthではstrategyファイルを元に何処にアクセスして、どの情報を取ってくるかなどを決めます。

まずはdeviseのイニシャライザでoauthの設定を行い、どのstrategyを使うかを記述します。


config/initializer/devise.rb

# strategyファイルのパスを指定して、ロード

require File.expand_path('lib/omniauth/strategies/doorkeeper', Rails.root)
Devise.setup do |config|
config.omniauth(:doorkeeper, 登録したAPP_ID, 登録したAPP_SECRET)
end

次にstrategyファイルを作ります。


lib/omniauth/strategies/doorkeeper.rb

module OmniAuth

module Strategies
class Doorkeeper < OmniAuth::Strategies::OAuth2
option :name, :doorkeeper # strategyの名前 ここで指定した名前をdeviseで呼び出す
option :client_options, site: provider側アプリのルートURL, authorize_path: '/oauth/authorize'

# uidとして設定するデータを指定
uid { raw_info['user']['id'] }
# providerから送られてきたデータの内、どれを使いたいか
info do
{ email: raw_info['user']['email'] }
end

# providerのAPIを叩いて、データを取ってくる
def raw_info
@raw_info ||= access_token.get('/api/login.json').parsed
end
end
end
end



コールバック処理を書く

認証に成功してコールバックしてきた際に行う処理を実装します。認証によって得られたトークンを使って、APIを叩き、ユーザーデータを取得、そのデータを用いてclient側アプリのユーザーを登録し、ログインさせます。

まずはコールバック処理をカスタマイズするために、ルーティングを設定し、独自のファイルを使うようにします。


config/routes.rb

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


次にそのファイルの内容を記述します。

認証によって得られたユーザー情報でclient側のユーザーを検索し、ヒットすればそのユーザーでログインします。ヒットしない場合は新規作成してログインします。


app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

def doorkeeper # メソッド名はstrategyで指定した名前
@user = User.find_or_create_with_doorkeeper(request.env['omniauth.auth'])

if @user.persisted?
sign_in(@user)
set_flash_message(:notice, :success, kind: 'doorkeeper') if is_navigational_format?
redirect_to root_url
else
session['devise.doorkeeper_data'] = request.env['omniauth.auth']
redirect_to root_url, alert: 'Doorkeeper ログインに失敗しました'
end
end
end



app/models/user.rb

class User < ActiveRecord::Base

def self.find_or_create_with_doorkeeper(auth)
user = self.find_by(provider: auth.provider, uid: auth.uid )
return user unless user.nil?

self.create(
email: auth.info.user.email,
provider: auth.provider,
uid: auth.uid,
password: Devise.friendly_token[0, 20]
)
end
end


最後に、ビューにuser_omniauth_authorize_path(provider)を呼び出すリンクを作れば完成です!


view

<%= link_to 'オレオレアカウントでログイン', user_omniauth_authorize_path(:doorkeeper) %>



幾つかの詰まりやすい点


ローカルでテストするときはvagrantのprivate_networkのipを別々に設定する

vagrantを2個立てて、vagrant間で通信できるように、ipを別々に割り当てて下さい。また、先に立てたvagrantは後に立てたvagrantのipを認識できない?のでprovider側アプリのサーバーとなるvagrantを先に立ててください。最後に、いつものようにlocalhostではアクセスせずに、vagrantのIPでアクセスした方が上手くいきやすいです。


staging, productionの時はhttpsで

ローカルの場合を除き、doorkeeperで設定したアプリケーションのコールバックURLはhttpsで無いといけないので、SSL通信の設定が必要です。


最後に

幾つか面倒な点はありますが、自前で実装するよりかは楽だと思うので、是非試してみて下さい。APIの実装もdoorkeeperを用いればだいぶ楽ですし、2つのアプリを連携させることで、出来ることの幅が大きくなると思います。

あと、この記事書くの疲れました0(:3 )〜

ブログでは技術Tips以外の記事も定期的にポストしています。

もしよかったらブログもどうぞ╭( ・ㅂ・)و

https://hackalive.net