Help us understand the problem. What is going on with this article?

Rails + devise + omniauth + doorkeeperでoauth認証を行う

More than 1 year has passed since last update.

この記事はLIFULL Advent Calendar その3の24日目の記事です。

概要

・認証周りについて動く物を作りながら理解を深めたいなと、RailsでOAuthによるログイン機能のサンプル作りました
・作るに辺り先人の記事が大いに参考になったのですが所々々ハマった箇所もあり、より詳細に手順を残しておければとまとめました

ゴール

1 認証を行うproviderをlocalhost:3000に構築する
2 providerの認証を得ることでログインできるclientサーバをlocalhost:3001に構築する
3 1で認証を行ったのち、2のコンテンツにアクセスする

実行環境

MacOS High Sierra 10.13
Rails version: 5.2.2
Ruby version: 2.4.0 (x86_64-darwin17)

構築手順

Ruby on Railsのインストール

こちらの記事を参考にさせていただきました。
https://qiita.com/yoshiokatsuneo@github/items/d75d4fef9e75afb65126#mac%E3%81%A7%E3%81%AErails%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

尚、Rails起動時に下記のようにRailsがinstallされてないよーと言われた際は

% rails
Rails is not currently installed on this system. To get the latest version, simply type:

rbenvのPathが通っていないので、下記の記事に習ってPathを通すといけます。

・zshの方は
https://qiita.com/seijikohara/items/79b479c9dd2e3b950301
・bashの方は
https://qiita.com/w7tree/items/0860e2856f2429b20eee

Providerサーバ構築

これ以降の作業は下記の記事が大変に参考になりました。
provider側は基本的に同じ内容になりますが、client側の細かい部分で追加作業が必要になります。
https://qiita.com/8398a7/items/9b13ac2e7c401dee4a39

尚、以後はホームディレクトリ内にworkspaceというディレクトリを切って作業していきます。
この場所はお好みでOKです。

% mkdir ~/workspace && cd ~/workspace

provider用に、workspace内でRailsプロジェクトを新規作成します。
(後ほど同じ場所にclientsプロジェクトも作っていくことになります)

% rails new provider

providerディレクトリが出来ていれば成功です

% ls
provider

移動しておく

% cd proveder

私の端末にRailsをインストールしたのは初めてだったので、一度起動してみました

% rail s

http://localhost:3000
にアクセス

スクリーンショット 2018-12-24 9.24.17.png

Yay出来ていたら成功です。うれしい

正常に起動が出来ていることが確認できたら、「Ctrl-C」で、サーバを一度停止します

必要なGemをインストールしていきます
Gemfileの末尾に下記を追記します

~/workspace/provider/Gemfile
gem 'devise'
gem 'doorkeeper'

deviseのインストールとUserテーブルの作成を行います

% rails g devise:install
% rails g devise User
% rake db:migrate

先の記事に習いUserテーブルにカラムを追加

% rails g migration AddOmniauthToUser uid name admin:boolean
% rake db:migrate

modelの作成を行います。
modeldevise用のオプションを指定することができます。
オプションの種類については下記にまとめてくださっています。
https://qiita.com/cigalecigales/items/73d7bd7ec59a001ccd74#2-devise%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E6%A6%82%E8%A6%81

下記のように修正

~/workspace/provider/app/models/user.rb
class User < ActiveRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  after_create :update_uid!

  def update_uid!
    update(uid: id)
  end
end

次はdoorkeeperのインストールとDB構築を行います。

% rails g doorkeeper:install
% rails g doorkeeper:migration
% rake db:migrate

doorkeeperの設定を行います。
Doorkeeper.configureブロック内、resource_owner_authenticatorとadmin_authenticatorを追記します

~/workspace/provider/config/initializers/doorkeeper.rb
Doorkeeper.configure do
  # Change the ORM that doorkeeper will use (needs plugins)
  orm :active_record

  resource_owner_authenticator do
    session[:return_to] = request.fullpath
    current_user || redirect_to(new_user_session_url)
  end 

  admin_authenticator do
    current_user.try(:admin) || redirect_to(new_user_session_url)
  end
end

ちなみにこのままだとrootへのURLが引けなくてdoorkeeperの管理画面でエラーを吐くので、routes.rbにrootの設定をしてあげます(ここでは仮のcontrollerにしておきます)

~/workspace/provider/config/routes.rb
 Rails.application.routes.draw do
   use_doorkeeper
   devise_for :users
   # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
   root to: 'home#show'
 end

認証用のUserを作成します
railsのコンソールから直接データを投入することが出来ます
ここで投入するemail、passwordを後ほど認証に利用します

% rails c 
> User.create(email: 'hoge@bar.com', password: 'hogehoge', name: 'fuga', admin: true)

これでdoorkeeperで必要な設定が出来ました。
「Ctrl-D」でコンソールを抜けた後に、Railsサーバを起動します

% rails s

起動出来たら下記にアクセスします。

http://localhost:3000/oauth/applications

まずdeviseにより、下記のような認証を求める画面に遷移します。
先ほどUserテーブルに投入したemail、paswordを入力してログインできます

スクリーンショット 2018-12-24 9.41.06.png

http://localhost:3000
に遷移してエラーが出るかと思いますが、認証には成功しています
(本筋には影響ないのでスルー)

改めて、下記にアクセス、doorkeeperの認証キーを作成するページに移動します。
http://localhost:3000/oauth/applications
スクリーンショット 2018-12-24 10.21.08.png

「New Application」から認証キーを新規発行します。
Name: Mock
Redirect URL: http://localhost:3001/users/auth/doorkeeper/callback
のように入力します
→(訂正) URL: http://localhost:3001/users/auth/mock/callback
スクリーンショット 2018-12-24 12.58.43.png

設定したらSubmitすると、Application UID、とSecretが発行されます。
この認証キーが漏れると第三者からのアクセスが可能となってしまうので、秘匿情報として扱うのが良いかと思います(localのみで実行する分には大丈夫ですが、念のため)。

最後にユーザ情報を取得するAPIを追加します

% rails g controller api/v1/api
~/workspace/provider/app/controllers/api/v1/api_controller.rb
class Api::V1::ApiController < ApplicationController
  private
  def current_resource_owner
    User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
  end 
end
% rails g controller api/v1/users
~/workspace/provider/app/controllers/api/v1/users_controller.rb
class Api::V1::UsersController < Api::V1::ApiController
  before_action :doorkeeper_authorize!
  respond_to :json

  def me
    respond_with current_resource_owner
  end 
end

ログイン後のリダイレクト処理を記述

% rails g controller users/sessions
~/workspace/provider/app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message(:notice, :signed_in) if is_flashing_format?
    sign_in(resource_name, resource)
    yield resource if block_given?
    if session[:return_to]
      redirect_to session[:return_to]
      session[:return_to] = nil 
    else
      respond_with resource, location: after_sign_in_path_for(resource)
    end 
  end 
end

最後にroutesを設定して完了です

~/workspace/provider/config/routes.rb
Rails.application.routes.draw do
  use_doorkeeper
  devise_for :users, controllers: {
    sessions: 'users/sessions'
  }
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  namespace :api do
    namespace :v1 do
      get '/me' => 'users#me'
    end
  end
  root to: 'home#show'
end

Client側サーバ構築

次にCLientsサーバを構築していきます

Railsプロジェクトを新規作成

%cd ~/.workspace/ && rails new client && cd client

deviseとomuniauthのインストール

Gemfileに必要なモジュールを記述
Railsプロジェクトを新規作成

~/workspace/client/Gemfile
gem 'devise'
gem 'omniauth'
gem 'omniauth-oauth2'

deviseインストールとUserテーブルの作成

% rails g devise:install
% rails g devise User
% rake db:migrate

omuniauthでの認証結果を格納するカラムを追加

% rails g migration AddOmniauthToUser uid provider name token raw:text
% rake db:migrate

starategiesの作成

workspace/client/lib/omniauth/strategies/mock.rb
module OmniAuth
  module Strategies
    class Mock < OmniAuth::Strategies::OAuth2
      RAW_INFO_URL = 'api/v1/me'

      uid { raw_info['uid'] }

      info do
        {
          email: raw_info['email'],
          name: raw_info['name']
        }
      end

      extra do
        { raw_info: raw_info }
      end

      def raw_info
        @raw_info ||= JSON.parse(access_token.get(RAW_INFO_URL).response.body)
      end
    end
  end
end

deviseの設定
provider側で先ほど発行したAPI_IDとAPP_SECRETを設定します

config/initializers/devise.rb
require File.expand_path('../../../lib/omniauth/strategies/mock', __FILE__)
Devise.setup do |config|
  config.omniauth :mock, 'APP_ID', 'APP_SECRET', client_options: { site: 'http://localhost:3000' }
end

providerで認証の後にコールバックされるURLの受け先を作成します

% rails g controller users/omniauth_callbacks

コントローラに下記を追記

~/workspace/client/app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def mock
    @user = User.find_for_mock(request.env['omniauth.auth'])

    if @user.persisted?
      flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: 'mock'
      sign_in_and_redirect @user, event: :authentication
    else
      session['devise.mock_data'] = request.env['omniauth.auth']
      redirect_to new_user_session_url, alert: @user.errors
    end 
  end 
end

下記Userモデルの作成
ここでdeviseのオプションに「:omuniauthable」を設定しておく必要があります

~/.workspace/client/app/models/user.rb
class User < ApplicationRecord
 devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :validatable, :omniauthable

 def self.find_for_mock(auth)
    parameters = { 
      name:     auth.info.name,
      email:    auth.info.email,
      provider: auth.provider,
      uid:      auth.uid,
      token:    auth.credentials.token,
      password: Devise.friendly_token[0, 20],
      raw: auth.extra.to_json
    }   
    user = User.find_by(uid: auth.uid)
    return update_mock!(user, parameters) if user

    User.create(parameters)
  end 

  def self.update_mock!(user, parameters)
    user.update(parameters)
    user
  end 
end

routingの設定をすれば完了です

~/workspace/client/config/route.rb
Rails.application.routes.draw do
  devise_for :users, :controllers => {
    :omniauth_callbacks => "users/omniauth_callbacks"
  }
end

3001番ポートでClient側サーバを起動します

% rails s -p 3001

http://localhost:3001/users/sign_in
にアクセスするとリンクに「Sign in with Mock」というリンクが表示されています
このリンクをクリックすると、Provider側の認証ページに遷移します
スクリーンショット 2018-12-24 14.07.36.png

認証先のページでAuthorizeをクリックすると、Providerサーバでの認証後にClientサーバへのコールバックが実行され、
localhost:3001
にリダイレクトされると思います

流れとしては、
http://localhost:3001/user/sign_in
↓ 「Sign in with Mock」をクリックすると下記リンクに遷移
http://localhost:3001/users/auth/mock
↓ 認証用URLにリダイレクト
http://localhost:3000/oauth/authorize
↓ ログインしていければ認証情報を入力、認証後、コールバック関数を実行
http://localhost:3001/users/auth/mock/callback?code=xxxxx

Client側でのログインを行わずに、Providerサーバでの認証を行えていることが分かります
(ChromeのNetWorkタブを利用すると分かりやすいです)
スクリーンショット 2018-12-24 14.32.32.png

おまけ

このままだと認可が与えられているか分かりにくいので、下記処理を追加しました。
今回は「localhost:3001/home」をログインしないと参照できないページと見立てています。
ログイン後に表示させるページ用のcontrollerを作成

bundle exec rails generate controller home index

Clientサーバ側のrouteに下記を追記してあげます。
「as: "user_root」をつけるとリダイレクト先のページを指定することができます。

~/workspace/client/config/route.rb
get "home", to: "home#index", as: "user_root

Controllerに認証済みのuserかを確認するように修正

~/workspace/app/controllers/home_controller.rb
class HomeController < ApplicationController
  before_action :authenticate_user!
  def index
  end
end

これで、「localhost:3001/home」にアクセスした際に認証が済んでいないと、ログイン画面に戻されるようになるかと思います。

参考

Rails導入周り
https://qiita.com/yoshiokatsuneo@github/items/d75d4fef9e75afb65126#mac%E3%81%A7%E3%81%AErails%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

Devise, Doorkeeper, omuniauth周り
https://tech.mof-mof.co.jp/blog/rails5-devise-doorkeeper.html
https://qiita.com/8398a7/items/9b13ac2e7c401dee4a39
https://qiita.com/kami_zh/items/94aec2d94a2b4e9a1d0b
https://qiita.com/kyonsuke19101/items/407f3cdfec38d1108e9d
https://easyramble.com/create-views-on-rails-devise.html
https://teratail.com/questions/143329

ログイン前後の処理周りは
https://qiita.com/ytkt/items/d78841f7dea5e29f38ee

おわりに

・勉強のために作リ始めたはずが結構時間がかかってしまった・・まだ理解が浅いのでもう少し深掘りしたい
・諸所でうまく行かない箇所を潰しながら進めたので、同じ手順で動かないなどあればご連絡いただけるとありがたいです
・参考にさせていただいた記事の執筆者の方々、大変ありがとうございます

moehiko
lifull
日本最大級の不動産・住宅情報サイト「LIFULL HOME'S」を始め、人々の生活に寄り添う様々な情報サービス事業を展開しています。
https://lifull.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away