#始めに
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
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を以下のように編集します。
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
となります。
###APIの作成
認証した後、client側アプリが使うためのAPIを作ります。今回はログイン処理なので、providerが持つユーザーデータを引き渡す処理を書きます。
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
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
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の処理を実装する
流れは
- deviseで作ったモデルにoauthを対応させる準備をする
- オレオレアプリ用のoauthのstrategyを作成する
- コールバック処理を書く
こんな感じ。
###deviseで作ったモデルにoauthを対応させる準備をする
ざっくり言うとfacebookログインに対応させるときと同じ処理です。
まず始めに、deviseで作ったモデルに:omniauthable
を追加します。
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
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を使うかを記述します。
# strategyファイルのパスを指定して、ロード
require File.expand_path('lib/omniauth/strategies/doorkeeper', Rails.root)
Devise.setup do |config|
config.omniauth(:doorkeeper, 登録したAPP_ID, 登録したAPP_SECRET)
end
次にstrategyファイルを作ります。
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側アプリのユーザーを登録し、ログインさせます。
まずはコールバック処理をカスタマイズするために、ルーティングを設定し、独自のファイルを使うようにします。
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
次にそのファイルの内容を記述します。
認証によって得られたユーザー情報でclient側のユーザーを検索し、ヒットすればそのユーザーでログインします。ヒットしない場合は新規作成してログインします。
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
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)
を呼び出すリンクを作れば完成です!
<%= 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