OAuth の環境を整備する

github 認証や google 認証など、アプリケーションへ既存の所有アカウントを利用してのログインを提供する機能に OAuth 認証があります。

今回は github 認証の実装を確認し、独自のプロバイダとそこへのクライアントを作成してみます。

目次

参考

github 認証するクライアントを作る

devise を導入したユーザー認証する環境を用意

毎度、User モデルを認証する環境を作っているので、割愛。
Ruby on Rails での認証(Devise ライブラリ)を参照してもらえるといい。

Client id と Client secret を入手

github にログインし、設定を開き「Developer settings」に進みます。

「Register a new application」からアプリケーションを登録します。

必要情報を登録します。

作成できた Client id と Client secret を控えておきます。

omniauth を導入する

それでは、github 認証を行うにあたって必要な gem omniauth を導入してみる。
以下を Gemfile に追記する。

Gemfile
1
2
gem 'omniauth'
gem 'omniauth-github', github: 'omniauth/omniauth-github', branch: 'master'

config/initializers/devise.rbを編集してプロバイダに github を登録します。

config/initializers/devise.rb
1
2
3
4
5
6
7
8
Devise.setup do |config|
# 省略

# 控えておいたClient id と Client secretを設定します。
config.omniauth :github, 'Client id', 'Client secret', scope: 'user,public_repo'

# 省略
end

User モデルに、omniauth 用のカラムを追加します。
以下の内容でマイグレーションをします。

マイグレーションファイル
1
2
3
4
5
6
class AddColumnsToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
end
end

app/models/user.rbを以下のように編集します。

app/models/user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class User < ApplicationRecord
# :omniauthable を追加します
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable

# providerとuidでuserを検索できるようにします
def self.find_for_github(auth)
user = User.find_or_create_by(provider: auth.provider, uid: auth.uid)

return user unless user.new_record?

user.uid = auth.uid
user.email = "#{auth.uid}-#{auth.provider}@fromgithub.com"
user.password = Devise.friendly_token[0, 20]

user.save
user

end
end

続いて github 認証後に実行されるコールバック処理を作成します。
app/controllers/omniauth_collbacks_contoroller.rbを以下の通り作成します。

app/controllers/omniauth_callbacks_contoroller.rb
1
2
3
4
5
6
7
8
9
10
11
12
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def github
@user = User.find_for_github(request.env['omniauth.auth'])

if @user.persisted?
sign_in_and_redirect @user, event: :authentication
else
session["devise.github_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end

作成したコールバック処理に転送されるように以下のようにルーティングを設定します。

config/routes.rb
1
2
3
4
5
Rails.application.routes.draw do
devise_for :users, :controllers => {
:omniauth_callbacks => "omniauth_callbacks"
}
end

github 認証確認

アプリケーションを起動し、http://localhost:3000/users/sign_inにアクセスします。

「Sign in with Github」をクリックします。
すると、以下の画面に進みます。

認証できると、devise でアプリケーションで設定したログイン後のリダイレクト先(何もしなければ root)にリダイレクトされます。

データベースを参照すると、User が 1 件増えていることが確認できます。

github 認証を実装できました。

プロバイダーを作ってみる

github 認証を実装できました。
今度は、github の代わりに認証自体をしてくれるプロバイダを作成してみます。

devise を導入したユーザー認証する環境を用意

こちらでも devise を使ったユーザー認証をする環境が必要なので、作成します。
Ruby on Rails での認証(Devise ライブラリ)を参照してもらえるといい。

ポート変更

ポート 3000 番で立ち上げるクライアントと同居させるので、プロバイダ側のポートを変更します。
今回は、5000 番にします。

config/puma.rb
1
port        ENV.fetch("PORT") { 5000 }

OAuth で使うカラムを追加

User モデルに、uidカラムを追加します。
以下の内容でマイグレーションをします。

マイグレーションファイル
1
2
3
4
5
class AddColumnsToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :uid, :string
end
end

追加した uid を設定するため、データが追加されたときに、uid を設定するようにしておきます。

app/models/user.rb
1
2
3
4
5
6
7
8
9
10
11
12
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable

after_create :set_uid

def set_uid
update(uid: Digest::SHA512.hexdigest(id.to_s))
end
end

doorkeeper を導入

OAuth のプロバイダの仕組みを提供する gem、doorkeeperを導入します。

Gemfile
1
gem 'doorkeeper'

gem が導入できたら、以下の手順で doorkeeper 用のマイグレーションまで進めます。

1
2
3
bundle exec rails g doorkeeper:install
bundle exec rails g doorkeeper:migration
bundle exec rails db:migrate

config/initializers/doorkeeper.rbが作成されているので、
以下のように編集します。

config/initializers/doorkeeper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Doorkeeper.configure do
orm :active_record

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

admin_authenticator do
#ログインしているユーザーが管理者でない場合リダイレクトする実装などをよく見かけますが、
#今回は確認だけなので制限なしに参照できることにします。
#当たり前ですが、本来は制限すべき項目です。
end

access_token_expires_in 2.hours

force_ssl_in_redirect_uri false
end

セッション作成時に、認証を要求したクライアントへリダイレクトする設定を作成します。

1
bundle exec rails g controller users/sessions

app/controllers/users/sessions_controller.rbが作成されるので、以下の様に編集します。
参考元から、そのまま使わせてもらいます。

app/controllers/users/sessions_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

続いて、データを提供する API を作成します。

app/controllers/api/api_controller.rb
app/controllers/api/users_controller.rb
の 2 つを作成します。

app/controllers/api/api_controller.rb
1
2
3
4
5
6
class Api::ApiController < ApplicationController
private
def current_resource_owner
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end
end
app/controllers/api/users_controller.rb
1
2
3
4
5
6
7
8
class Api::UsersController < Api::ApiController
before_action :doorkeeper_authorize!
respond_to :json

def info
respond_with current_resource_owner
end
end

最後に、ルーティングの設定を行います。
config/routes.rbを以下のように編集します。

config/routes.rb
1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do
use_doorkeeper
devise_for :users, controllers: {
sessions: 'users/sessions'
}
namespace :api do
get '/info' => 'users#info'
end
end

プロバイダ確認

アプリケーションを起動し、http://localhost:5000/users/sign_inにアクセスします。
ユーザーを作成します。

テーブルを確認して、uidが追加されていることを確認しておきます。

続いてhttp://localhost:5000/oauth/applicationsにアクセスします。
すると、以下の画面になります。

以下のように設定して、登録しておきます。

UID と Secret を控えておきます。

プロバイダを作成して、次に作るアプリケーションを登録できました。

自作プロバイダに対して、OAuth 認証してみる

最初に作成した github 認証したアプリケーションを自作したプロバイダでも認証できるようにします。

自作プロバイダを認証できるように調整する。

必要な gem omniauth-oauth2 を導入します。
以下を Gemfile に追記して、インストールします。

Gemfile
1
gem 'omniauth-oauth2'

omniouth が提供しないプロバイダで認証するには、ストラテジを自作する必要があります。
lib/omniauth/strategies/myapp.rb を作成します。

lib/omniauth/strategies/myapp.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
require 'omniauth-oauth2'

module OmniAuth
module Strategies
class Myapp < OmniAuth::Strategies::OAuth2
option :name, 'myapp'

RAW_INFO_URL = 'api/info'

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

config/initializers/devise.rb を編集して myapp をプロバイダとして登録します。

config/initializers/devise.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 明示的に呼び出さないと、読み込まなかったので注意
require File.expand_path('../../../lib/omniauth/strategies/myapp', __FILE__)

Devise.setup do |config|
# 省略

# 控えておいたClient id と Client secretを設定します。
config.omniauth :github, 'Client id', 'Client secret', scope: 'user,public_repo'
config.omniauth :myapp,
'UID',
'Secret',
client_options: { site: 'http://localhost:5000' },
strategy_class: OmniAuth::Strategies::Myapp,
scope: "test"
#使用するストラテジのクラスを明示的に指定します。

# 省略
end

app/models/user.rb を編集して、myapp 用の処理を作ります。

app/models/user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User < ApplicationRecord
# 省略

def self.find_for_myapp(auth)
user = User.find_or_create_by(provider: auth.provider, uid: auth.uid)

return user unless user.new_record?

user.uid = auth.uid
user.email = "#{auth.uid}-#{auth.provider}@myapp.com"
user.password = Devise.friendly_token[0, 20]

user.save
user

end

end

app/controllers/omniauth_callbacks_controller.rb を編集して myapp 用の処理を追加します。

app/controllers/omniauth_callbacks_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def github
@user = User.find_for_github(request.env['omniauth.auth'])

if @user.persisted?
sign_in_and_redirect @user, event: :authentication
else
session["devise.github_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end

def myapp
@user = User.find_for_myapp(request.env['omniauth.auth'])

if @user.persisted?
sign_in_and_redirect @user, event: :authentication
else
session["devise.github_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end

自作プロバイダで認証確認

クライアント側のアプリケーションで/users/sign_inにアクセスすると、「Sign in with Myapp」というリンクが増えているので、こちらにアクセスします。

すると、次のようにアクセスを許可するかの画面が表示されます。
「Authorize」をクリックします。

すると、クライアントのログイン後のリダイレクト先に戻ります。

データベースを確認すると、クライアントの user にプロバイダの user と uid の同じものが作成されています。

自作のプロバイダで認証できました。


github と自作プロバイダで OAuth 認証をしてみました。
最終的にできる動作確認ができるまで足かけ 3 日程かけてしまいました。

参照している資料そのままの部分の多いのですが、自身で認証のプロバイダの実装をしてみることで、ただ使っているだけよりも理解が深まったと感じます。

何かサービスを作ることがあれば、今回のようにユーザーの管理をプロバイダに任せ、アプリケーションを分離させるのもよさそうです。
ただ、OAuth での認証を提供しているのは Google や github の様に大規模なサービスばかりです。
だからこそ認証の機能を任せる意味があるのではと考えたところでした。

認証を以外の使い方も試みてみたいところです。

ではでは。